Yii Blog 1.1.0 Demo Tutor

60
Building a Blog System using Yii Qiang Xue Copyright 2008-2009. All Rights Reserved.

description

Basic yii tutorial, Demo Blog creation

Transcript of Yii Blog 1.1.0 Demo Tutor

Page 1: Yii Blog 1.1.0 Demo Tutor

Building a Blog System using Yii

Qiang Xue

Copyright 2008-2009 All Rights Reserved

Contents

Contents i

License v

1 Getting Started 1

11 Building a Blog System using Yii 1

12 Testdriving with Yii 1

121 Installing Yii 1

122 Creating Skeleton Application 2

123 Application Workflow 3

13 Requirements Analysis 4

14 Overall Design 5

2 Initial Prototyping 9

21 Setting Up Database 9

211 Creating Database 9

212 Establishing Database Connection 9

22 Scaffolding 11

23 Authenticating User 14

24 Summary 17

3 Post Management 19

ii Contents

31 Customizing Post Model 19

311 Customizing rules() Method 19

312 Customizing relations() Method 20

313 Representing Status in Text 21

32 Creating and Updating Posts 23

321 Customizing Access Control 23

322 Customizing create and update Operations 24

33 Displaying Posts 25

331 Customizing view Operation 25

332 Customizing index Operation 27

34 Managing Posts 28

341 Listing Posts in Tabular View 28

342 Deleting Posts 29

4 Comment Management 33

41 Customizing Comment Model 33

411 Customizing rules() Method 33

412 Customizing attributeLabels() Method 33

413 Customizing Saving Process 34

42 Creating and Displaying Comments 34

421 Displaying Comments 34

422 Creating Comments 35

43 Managing Comments 37

431 Updating and Deleting Comments 37

432 Approving Comments 37

Contents iii

5 Portlets 39

51 Customizing Page Layout 39

52 Creating User Menu Portlet 41

521 Creating UserMenu Class 41

522 Creating userMenu View 42

523 Using UserMenu Portlet 42

524 Testing UserMenu Portlet 43

525 Summary 43

53 Creating Tag Cloud Portlet 43

531 Creating TagCloud Class 43

532 Using TagCloud Portlet 44

54 Creating Recent Comments Portlet 45

541 Creating RecentComments Class 45

542 Creating recentComments View 46

543 Using RecentComments Portlet 46

6 Final Work 47

61 Beautifying URLs 47

62 Logging Errors 48

63 Final Tune-up and Deployment 49

631 Changing Home Page 49

632 Enabling Schema Caching 49

633 Disabling Debugging Mode 50

634 Deploying the Application 50

64 Future Enhancements 51

iv Contents

641 Using a Theme 51

642 Internationalization 51

643 Improving Performance with Cache 51

644 Adding New Features 52

License of Yii

The Yii framework is free software It is released under the terms of the following BSDLicense

Copyright ccopy2008-2009 by Yii Software LLC All rights reserved

Redistribution and use in source and binary forms with or without modification arepermitted provided that the following conditions are met

1 Redistributions of source code must retain the above copyright notice this list ofconditions and the following disclaimer

2 Redistributions in binary form must reproduce the above copyright notice this list ofconditions and the following disclaimer in the documentation andor other materialsprovided with the distribution

3 Neither the name of Yii Software LLC nor the names of its contributors may beused to endorse or promote products derived from this software without specificprior written permission

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS rdquoAS

ISrdquo AND ANY EXPRESS OR IMPLIED WARRANTIES INCLUDING BUT NOT LIMITED TO THE

IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PUR-

POSE ARE DISCLAIMED IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBU-

TORS BE LIABLE FOR ANY DIRECT INDIRECT INCIDENTAL SPECIAL EXEMPLARY OR

CONSEQUENTIAL DAMAGES (INCLUDING BUT NOT LIMITED TO PROCUREMENT OF SUB-

STITUTE GOODS OR SERVICES LOSS OF USE DATA OR PROFITS OR BUSINESS INTERRUP-

TION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY WHETHER IN CONTRACT

STRICT LIABILITY OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY

WAY OUT OF THE USE OF THIS SOFTWARE EVEN IF ADVISED OF THE POSSIBILITY OF

SUCH DAMAGE

vi Contents

Chapter 1

Getting Started

11 Building a Blog System using Yii

This tutorial describes how to use Yii to develop a blog application shown as the blog demowhich can be found in the Yii release files It explains in detail every step to be takenduring the development which may also be applied in developing other Web applicationsAs a complement to the Guide and the Class Reference of Yii this tutorial aims to showpractical usage of Yii instead of thorough and definitive description

Readers of this tutorial are not required to have prior knowledge about Yii However basicknowledge of object-oriented programming (OOP) and database programming would helpreaders to understand the tutorial more easily

This tutorial is released under the Terms of Yii Documentation

12 Testdriving with Yii

In this section we describe how to create a skeleton application that will serve as ourstarting point For simplicity we assume that the document root of our Web server iswwwroot and the corresponding URL is httpwwwexamplecom

121 Installing Yii

We first install the Yii framework Grab a copy of the Yii release file (version 110 orabove) from wwwyiiframeworkcom and unpack it to the directory wwwrootyii Doublecheck to make sure that there is a directory wwwrootyiiframework

Tip The Yii framework can be installed anywhere in the file system not necessarilyunder a Web folder Its framework directory contains all framework code and isthe only framework directory needed when deploying an Yii application A singleinstallation of Yii can be used by multiple Yii applications

2 1 Getting Started

After installing Yii open a browser window and access the URL httpwwwexample

comyiirequirementsindexphp It shows the requirement checker provided in the Yiirelease For our blog application besides the minimal requirements needed by Yii we alsoneed to enable both the pdo and pdo sqlite PHP extensions so that we can access SQLitedatabases

122 Creating Skeleton Application

We then use the yiic tool to create a skeleton application under the directory wwwroot

blog The yiic tool is a command line tool provided in the Yii release It can be used togenerate code to reduce certain repetitive coding tasks

Open a command window and execute the following command

wwwrootyiiframeworkyiic webapp wwwrootblog

Create a Web application under rsquowwwrootblogrsquo [Yes|No]y

Tip In order to use the yiic tool as shown above the CLI PHP program must beon the command search path If not the following command may be used instead

pathtophp wwwrootyiiframeworkyiicphp webapp wwwrootblog

To try out the application we just created open a Web browser and navigate to the URLhttpwwwexamplecomblogindexphp We should see that our skeleton applicationalready has four fully functional pages the homepage the about page the contact pageand the login page

In the following we briefly describe what we have in this skeleton application

Entry Script

We have an entry script file wwwrootblogindexphp which has the following content

ltphp

$yii=rsquowwwrootframeworkyiiphprsquo

$config=dirname( FILE )rsquoprotectedconfigmainphprsquo

remove the following line when in production mode

defined(rsquoYII DEBUGrsquo) or define(rsquoYII DEBUGrsquotrue)

12 Testdriving with Yii 3

require once($yii)

YiicreateWebApplication($config)-gtrun()

This is the only script that Web users can directly access The script first includes theYii bootstrap file yiiphp It then creates an application instance with the specifiedconfiguration and executes the application

Base Application Directory

We also have an application base directory wwwrootblogprotected The majority of ourcode and data will be placed under this directory and it should be protected from beingaccessed by Web users For Apache httpd Web server we place under this directory ahtaccess file with the following content

deny from all

For other Web servers please refer to the corresponding manual on how to protect adirectory from being accessed by Web users

123 Application Workflow

To help understand how Yii works we describe the main workflow in our skeleton appli-cation when a user is accessing its contact page

1 The user requests the URL httpwwwexamplecomblogindexphpr=sitecontact

2 The entry script is executed by the Web server to process the request

3 An application instance is created and configured with initial property values speci-fied in the application configuration file wwwrootblogprotectedconfigmainphp

4 The application resolves the request into a controller and a controller action For thecontact page request it is resolved as the site controller and the contact action (theactionContact method in wwwrootblogprotectedcontrollersSiteControllerphp)

5 The application creates the site controller in terms of a SiteController instanceand then executes it

6 The SiteController instance executes the contact action by calling its actionContact()method

4 1 Getting Started

7 The actionContact method renders a view named contact to the Web user Inter-nally this is achieved by including the view file wwwrootblogprotectedviews

sitecontactphp and embedding the result into the layout file wwwrootblog

protectedviewslayoutsmainphp

13 Requirements Analysis

The blog system that we are going to develop is a single user system The owner of thesystem will be able to perform the following actions

bull Login and logout

bull Create update and delete posts

bull Publish unpublish and archive posts

bull Approve and delete comments

All other users are guest users who can perform the following actions

bull Read posts

bull Create comments

Additional Requirements for this system include

bull The homepage of the system should display a list of the most recent posts

bull If a page contains more than 10 posts they should be displayed in pages

bull The system should display a post together with its comments

bull The system should be able to list posts with a specified tag

bull The system should show a cloud of tags indicating their use frequencies

bull The system should show a list of most recent comments

bull The system should be themeable

bull The system should use SEO-friendly URLs

14 Overall Design 5

14 Overall Design

Based on the analysis of the requirements we decide to use the following database tablesto store the persistent data for our blog application

bull tbl user stores the user information including username and password

bull tbl post stores the blog post information It mainly consists of the following columns

ndash title required title of the post

ndash content required body content of the post which uses the Markdown format

ndash status required status of the post which can be one of following values

lowast 1 meaning the post is in draft and is not visible to public

lowast 2 meaning the post is published to public

lowast 3 meaning the post is outdated and is not visible in the post list (stillaccessible individually though)

ndash tags optional a list of comma-separated words categorizing the post

bull tbl comment stores the post comment information Each comment is associated witha post and mainly consists of the following columns

ndash name required the author name

ndash email required the author email

ndash website optional the author website URL

ndash content required the comment content in plain text format

ndash status required status of the comment which indicates whether the commentis approved (value 2) or not (value 1)

bull tbl tag stores post tag frequency information that is needed to implement the tagcloud feature The table mainly contains the following columns

ndash name required the unique tag name

ndash frequency required the number of times that the tag appears in posts

bull tbl lookup stores generic lookup information It is essentially a map between integervalues and text strings The former is the data representation in our code while thelatter is the corresponding presentation to end users For example we use integer1 to represent the draft post status and string Draft to display this status to endusers This table mainly contains the following columns

6 1 Getting Started

ndash name the textual representation of the data item that is to be displayed to endusers

ndash code the integer representation of the data item

ndash type the type of the data item

ndash position the relative display order of the data item among other items of thesame type

The following entity-relation (ER) diagram shows the table structure and relationshipsabout the above tables

Figure 11 Entity-Relation Diagram of the Blog Database

Complete SQL statements corresponding to the above ER diagram may be found inthe blog demo In our Yii installation they are in the file wwwrootyiidemosblog

protecteddataschemasqlitesql

Info We name all our table names and column names in lower case This is becausedifferent DBMS often have different case-sensitivity treatment and we want to avoidtroubles like this

We also prefix all our tables with tbl This serves for two purposes First the prefixintroduces a namespace to these tables in case when they need to coexist with othertables in the same database which often happens in a shared hosting environmentwhere a single database is being used by multiple applications Second using tableprefix reduces the possibility of having some table names that are reserved keywordsin DBMS

14 Overall Design 7

We divide the development of our blog application into the following milestones

bull Milestone 1 creating a prototype of the blog system It should consist of most ofthe required functionalities

bull Milestone 2 completing post management It includes creating listing showingupdating and deleting posts

bull Milestone 3 completing comment management It includes creating listing ap-proving updating and deleting post comments

bull Milestone 4 implementing portlets It includes user menu login tag cloud andrecent comments portlets

bull Milestone 5 final tune-up and deployment

8 1 Getting Started

Chapter 2

Initial Prototyping

21 Setting Up Database

Having created a skeleton application and finished the database design in this section wewill create the blog database and establish the connection to it in the skeleton application

211 Creating Database

We choose to create a SQLite database Because the database support in Yii is built on topof PDO we can easily switch to use a different type of DBMS (eg MySQL PostgreSQL)without the need to change our application code

We create the database file blogdb under the directory wwwrootblogprotecteddataNote that both the directory and the database file have to be writable by the Web serverprocess as required by SQLite We may simply copy the database file from the blogdemo in our Yii installation which is located at wwwrootyiidemosblogprotecteddatablogdb We may also generate the database by executing the SQL statements in the filewwwrootyiidemosblogprotecteddataschemasqlitesql

Tip To execute SQL statements we may use the sqlite3 command line tool thatcan be found in the SQLite official website

212 Establishing Database Connection

To use the blog database in the skeleton application we created we need to modify itsapplication configuration which is stored in the PHP script wwwrootblogprotected

configmainphp The script returns an associative array consisting of name-value pairseach of which is used to initialize a writable property of the application instance

We configure the db component as follows

10 2 Initial Prototyping

return array(

rsquocomponentsrsquo=gtarray(

rsquodbrsquo=gtarray(

rsquoconnectionStringrsquo=gtrsquosqlitewwwrootblogprotecteddatablogdbrsquo

rsquotablePrefixrsquo=gtrsquotbl rsquo

)

)

)

The above configuration says that we have a db application component whose connectionStringproperty should be initialized as sqlitewwwrootblogprotecteddatablogdb and whosetablePrefix property should be tbl

With this configuration we can access the DB connection object using Yiiapp()-gtdb

at any place in our code Note that Yiiapp() returns the application instance that wecreate in the entry script If you are interested in possible methods and properties that theDB connection has you may refer to its class reference However in most cases we are notgoing to use this DB connection directly Instead we will use the so-called ActiveRecordto access the database

We would like to explain a bit more about the tablePrefix property that we set in theconfiguration This tells the db connection that it should respect the fact we are usingtbl as the prefix to our database table names In particular if in a SQL statement thereis a token enclosed within double curly brackets (eg post then the db connectionshould translate it into a name with the table prefix (eg tbl post) before sending it toDBMS for execution This feature is especially useful if in future we need to modify thetable name prefix without touching our source code For example if we are developing ageneric content management system (CMS) we may exploit this feature so that when itis being installed in a new environment we can allow users to choose a table prefix theylike

22 Scaffolding 11

Tip If you want to use MySQL instead of SQLite to store data you may cre-ate a MySQL database named blog using the SQL statements in wwwrootyii

demosblogprotecteddataschemamysqlsql Then modify the applicationconfiguration as follows

return array(

rsquocomponentsrsquo=gtarray(

rsquodbrsquo=gtarray(

rsquoconnectionStringrsquo =gt rsquomysqlhost=localhostdbname=blogrsquo

rsquoemulatePreparersquo =gt true

rsquousernamersquo =gt rsquorootrsquo

rsquopasswordrsquo =gt rsquorsquo

rsquocharsetrsquo =gt rsquoutf8rsquo

rsquotablePrefixrsquo =gt rsquotbl rsquo

)

)

)

22 Scaffolding

Create read update and delete (CRUD) are the four basic operations of data objects in anapplication Because the task of implementing the CRUD operations is so common whendeveloping Web applications Yii provides a tool to automate this process (also known asscaffolding) In this section we will describe how to use the tool to implement CRUD forposts and comments

Open a command window and run the following commands

wwwrootyiiframeworkyiic shell wwwrootblogindexphp

Yii Interactive Tool v11

Please type rsquohelprsquo for help Type rsquoexitrsquo to quit

gtgt model

gtgt crud Post

gtgt crud Comment

gtgt exit

12 2 Initial Prototyping

Info Some PHP installations may use a different phpini file for command line(CLI) PHP parser As a result when running the above yiic commands youmay encounter errors like rdquoYiiBaseinclude(PDOphp) failed to open streamrdquoor rdquocould not find driverrdquo Please double check your CLI PHP configuration byexecuting the following command

php -r phpinfo()

The result of the above command will show which phpini file is being used andwhich extensions are loaded If a wrong phpini file is used you may use thefollowing command to explicitly specify the correct phpini to use

php -c phpini wwwrootyiiframeworkyiicphp shell wwwrootblogindexphp

The commands above accomplish two tasks First the model command generates a modelclass file for each table in the blog database Second the crud commands generate thecode needed by the CRUD operations for the Post and Comment models

Tip The command model generates a model class for every table in the databaseSometimes we probably donrsquot want to do so (eg the database contains some irrel-evant tables) In this case we can create model classes one by one For exampleto create the User model we can use the command model User The model com-mand also has some more advanced usages For more details execute help model

command

We can test the generated code by accessing the following URLs

httpwwwexamplecomblogindexphpr=post

httpwwwexamplecomblogindexphpr=comment

Notice that the post and comment features implemented by the generated code are com-pletely independent of each other Also when creating a new post or comment we arerequired to enter information such as author id and create time which in real applica-tion should be set by the program Donrsquot worry We will fix these problems in the nextmilestones For now we should be fairly satisfied as this prototype already contains mostfeatures that we need to implement for the blog application

To prepare for the next milestones letrsquos take a closer look at the files generated by theabove commands All the files are generated under wwwrootblogprotected For conve-nience we group them into model files controller files and view files

22 Scaffolding 13

bull model files

ndash modelsUserphp contains the User class that extends from CActiveRecord andcan be used to access the tbl user database table

ndash modelsPostphp contains the Post class that extends from CActiveRecord andcan be used to access the tbl post database table

ndash modelsTagphp contains the Tag class that extends from CActiveRecord andcan be used to access the tbl tag database table

ndash modelsCommentphp contains the Comment class that extends from CActiveRe-cord and can be used to access the tbl comment database table

ndash modelsLookupphp contains the Lookup class that extends from CActiveRecordand can be used to access the tbl lookup database table

bull controller file

ndash controllersPostControllerphp contains the PostController class which is thecontroller in charge of all CRUD operations about posts

ndash controllersCommentControllerphp contains the CommentController class whichis the controller in charge of all CRUD operations about comments

bull view files

ndash viewspostcreatephp is the view file that shows an HTML form to create anew post

ndash viewspostupdatephp is the view file that shows an HTML form to update anexisting post

ndash viewspostviewphp is the view file that displays the detailed information of apost

ndash viewspostindexphp is the view file that displays a list of posts

ndash viewspostadminphp is the view file that displays posts in a table with ad-ministrative commands

ndash viewspost formphp is the partial view file embedded in viewspostcreate

php and viewspostupdatephp It displays the HTML form for collecting postinformation

ndash viewspost viewphp is the partial view file used by viewspostindexphp Itdisplays the brief view of a single post

ndash a similar set of view files are also generated for comment

14 2 Initial Prototyping

In order to understand better how the above files are used we show in the following theworkflow that occurs in the blog application when displaying a list of posts

1 The user requests the URL httpwwwexamplecomblogindexphpr=posts

2 The entry script is executed by the Web server which creates and initializes anapplication instance to handle the request

3 The application creates an instance of PostController and executes it

4 The PostController instance executes the index action by calling its actionIndex()

method Note that index is the default action if the user does not specify an actionto execute in the URL

5 The actionIndex() method queries database to bring back the list of recent posts

6 The actionIndex() method renders the index view with the post data

23 Authenticating User

Our blog application needs to differentiate between the system owner and guest usersTherefore we need to implement the user authentication feature

As you may have found that the skeleton application already provides user authenticationby checking if the username and password are both demo or admin In this section wewill modify the corresponding code so that the authentication is done against the User

database table

User authentication is performed in a class implementing the IUserIdentity interface Theskeleton application uses the UserIdentity class for this purpose The class is stored inthe file wwwrootblogprotectedcomponentsUserIdentityphp

Tip By convention the name of a class file must be the same as the correspondingclass name suffixed with the extension php Following this convention one canrefer to a class using a path alias For example we can refer to the UserIdentity

class with the alias applicationcomponentsUserIdentity Many APIs in Yiican recognize path aliases (eg YiicreateComponent()) and using path aliasesavoids the necessity of embedding absolute file paths in the code The existence ofthe latter often causes trouble when we deploy an application

We modify the UserIdentity class as follows

23 Authenticating User 15

ltphp

class UserIdentity extends CUserIdentity

private $ id

public function authenticate()

$username=strtolower($this-gtusername)

$user=Usermodel()-gtfind(rsquoLOWER(username)=rsquoarray($username))

if($user===null)

$this-gterrorCode=selfERROR USERNAME INVALID

else if($user-gtvalidatePassword($this-gtpassword))

$this-gterrorCode=selfERROR PASSWORD INVALID

else

$this-gt id=$user-gtid

$this-gtusername=$user-gtusername

$this-gterrorCode=selfERROR NONE

return $this-gterrorCode==selfERROR NONE

public function getId()

return $this-gt id

In the authenticate() method we use the User class to look for a row in the User tablewhose username column is the same as the given username in a case-insensitive mannerRemember that the User class was created using the yiic tool in the prior section Becausethe User class extends from CActiveRecord we can exploit the ActiveRecord feature toaccess the User table in an OOP fashion

In order to check if the user has entered a valid password we invoke the validatePassword

method of the User class We need to modify the file wwwrootblogprotectedmodels

Userphp as follows Note that instead of storing the plain password in the database westore the hash result of the password and a randomly generated salt key When validatingthe user-entered password we should compare the hash results instead

class User extends CActiveRecord

public function validatePassword($password)

return $this-gthashPassword($password$this-gtsalt)===$this-gtpassword

16 2 Initial Prototyping

public function hashPassword($password$salt)

return md5($salt$password)

In the UserIdentity class we also override the getId() method which returns the id valueof the user found in the User table The parent implementation would return the usernameinstead Both the username and id properties will be stored in the user session and maybe accessed via Yiiapp()-gtuser from anywhere in our code

Tip In the UserIdentity class we reference the class CUserIdentity without ex-plicitly including the corresponding class file This is because CUserIdentity is acore class provided by the Yii framework Yii will automatically include the classfile for any core class when it is referenced for the first time

We also do the same with the User class This is because the User class file is placedunder the directory wwwrootblogprotectedmodels which has been added tothe PHP include path according to the following lines found in the applicationconfiguration

return array(

rsquoimportrsquo=gtarray(

rsquoapplicationmodelsrsquo

rsquoapplicationcomponentsrsquo

)

)

The above configuration says that any class whose class file is located under either wwwrootblogprotectedmodels or wwwrootblogprotectedcomponents willbe automatically included when the class is referenced for the first time

The UserIdentity class is mainly used by the LoginForm class to authenticate a user basedon the username and password input collected from the login page The following codefragment shows how UserIdentity is used

$identity=new UserIdentity($username$password)

$identity-gtauthenticate()

switch($identity-gterrorCode)

24 Summary 17

case UserIdentityERROR NONE

Yiiapp()-gtuser-gtlogin($identity)

break

Info People often get confused about identity and the user application componentThe former represents a way of performing authentication while the latter is usedto represent the information related with the current user An application can onlyhave one user component but it can have one or several identity classes dependingon what kind of authentication it supports Once authenticated an identity instancemay pass its state information to the user component so that they are globallyaccessible via user

To test the modified UserIdentity class we can browse the URL httpwwwexample

comblogindexphp and try logging in with the username and password that we store inthe User table If we use the database provided by the blog demo we should be able tologin with username demo and password demo Note that this blog system does not providethe user management feature As a result a user cannot change his account or create anew one through the Web interface The user management feature may be considered asa future enhancement to the blog application

24 Summary

We have completed the milestone 1 Letrsquos summarize what we have done so far

1 We identified the requirements to be fulfilled

2 We installed the Yii framework

3 We created a skeleton application

4 We designed and created the blog database

5 We modified the application configuration by adding the database connection

6 We generated the code that implements the basic CRUD operations for both postsand comments

7 We modified the authentication method to check against the tbl user table

18 2 Initial Prototyping

For a new project most of the time will be spent in step 1 and 4 for this first milestone

Although the code generated by the yiic tool implements fully functional CRUD oper-ations for a database table it often needs to be modified in practical applications Forthis reason in the next two milestone our job is to customize the generated CRUD codeabout posts and comments so that it reaches our initial requirements

In general we first modify the model class file by adding appropriate validation rules anddeclaring relational objects We then modify the controller action and view code for eachindividual CRUD operation

Chapter 3

Post Management

31 Customizing Post Model

The Post model class generated by the yiic tool mainly needs to be modified in two places

bull the rules() method specifies the validation rules for the model attributes

bull the relations() method specifies the related objects

Info A model consists of a list of attributes each associated with a column in thecorresponding database table Attributes can be declared explicitly as class membervariables or implicitly without any declaration

311 Customizing rules() Method

We first specify the validation rules which ensure the attribute values entered by usersare correct before they are saved to the database For example the status attribute ofPost should be an integer 1 2 or 3 The yiic tool also generates validation rules for eachmodel However these rules are based on the table column information and may not beappropriate

Based on the requirement analysis we modify the rules() method as follows

public function rules()

return array(

array(rsquotitle content statusrsquo rsquorequiredrsquo)

array(rsquotitlersquo rsquolengthrsquo rsquomaxrsquo=gt128)

array(rsquostatusrsquo rsquoinrsquo rsquorangersquo=gtarray(123))

array(rsquotagsrsquo rsquomatchrsquo rsquopatternrsquo=gtrsquo^[ws]+$rsquorsquomessagersquo=gtrsquoTags can only contain word charactersrsquo)

20 3 Post Management

array(rsquotagsrsquo rsquonormalizeTagsrsquo)

)

In the above we specify that the title content and status attributes are required thelength of title should not exceed 128 the status attribute value should be 1 (draft) 2(published) or 3 (archived) and the tags attribute should only contain word charactersand commas In addition we use normalizeTags to normalize the user-entered tags so thatthe tags are unique and properly separated with commas

The validators such as required length in and match are all built-in validators providedby Yii The normalizeTags validator is a method-based validator that we need to definein the Post class For more information about how to specify validation rules please referto the Guide

public function normalizeTags($attribute$params)

$this-gttags=Tagarray2string(array unique(Tagstring2array($this-gttags)))

The rules declared in this method are executed one by one when we call the validate() orsave() method of the model instance

Note It is very important to remember that attributes appearing in rules() mustbe those to be entered by end users Other attributes such as id and create time

in the Post model which are set by our code or database should not be in rules()For more details please refer to Securing Attribute Assignments

After making these changes we can visit the post creation page again to verify that thenew validation rules are taking effect

312 Customizing relations() Method

Lastly we customize the relations() method to specify the related objects of a post Bydeclaring these related objects in relations() we can exploit the powerful RelationalActiveRecord (RAR) feature to access the related object information of a post such asits author and comments without the need to write complex SQL JOIN statements

We customize the relations() method as follows

31 Customizing Post Model 21

public function relations()

return array(

rsquoauthorrsquo =gt array(selfBELONGS TO rsquoUserrsquo rsquoauthor idrsquo)

rsquocommentsrsquo =gt array(selfHAS MANY rsquoCommentrsquo rsquopost idrsquo

rsquoconditionrsquo=gtrsquocommentsstatus=rsquoCommentSTATUS APPROVED

rsquoorderrsquo=gtrsquocommentscreate time DESCrsquo)

rsquocommentCountrsquo =gt array(selfSTAT rsquoCommentrsquo rsquopost idrsquo

rsquoconditionrsquo=gtrsquostatus=rsquoCommentSTATUS APPROVED)

)

The above relations state that

bull A post belongs to an author whose class is User and the relationship is establishedbased on the author id attribute value of the post

bull A post has many comments whose class is Comment and the relationship is establishedbased on the post id attribute value of the comments These comments should besorted according to their creation time and the comments must be approved

bull The commentCount relation is a bit special as it returns back an aggregation resultwhich is about how many comments the post has

With the above relation declaration we can easily access the author and comments of apost like the following

$author=$post-gtauthor

echo $author-gtusername

$comments=$post-gtcomments

foreach($comments as $comment)

echo $comment-gtcontent

For more details about how to declare and use relations please refer to the Guide

313 Representing Status in Text

Because the status of a post is stored as an integer in the database we need to provide atextual representation so that it is more intuitive when being displayed to end users In alarge system the similar requirement is very common

22 3 Post Management

As a generic solution we use the tbl lookup table to store the mapping between integervalues and textual representations that are needed by other data objects We modify theLookup model class as follows to more easily access the textual data in the table

class Lookup extends CActiveRecord

private static $ items=array()

public static function items($type)

if(isset(self$ items[$type]))

selfloadItems($type)

return self$ items[$type]

public static function item($type$code)

if(isset(self$ items[$type]))

selfloadItems($type)

return isset(self$ items[$type][$code]) self$ items[$type][$code] false

private static function loadItems($type)

self$ items[$type]=array()

$models=selfmodel()-gtfindAll(array(

rsquoconditionrsquo=gtrsquotype=typersquo

rsquoparamsrsquo=gtarray(rsquotypersquo=gt$type)

rsquoorderrsquo=gtrsquopositionrsquo

))

foreach($models as $model)

self$ items[$type][$model-gtcode]=$model-gtname

Our new code mainly provides two static methods Lookupitems() and Lookupitem()The former returns a list of strings belonging to the specified data type while the latterreturns a particular string for the given data type and data value

Our blog database is pre-populated with two lookup types PostStatus and CommentStatusThe former refers to the possible post statuses while the latter the comment statuses

In order to make our code easier to read we also declare a set of constants to representthe status integer values We should use these constants through our code when referringto the corresponding status values

32 Creating and Updating Posts 23

class Post extends CActiveRecord

const STATUS DRAFT=0

const STATUS PUBLISHED=1

const STATUS ARCHIVED=2

Therefore we can call Lookupitems(rsquoPostStatusrsquo) to get the list of possible post statuses(text strings indexed by the corresponding integer values) and call Lookupitem(rsquoPostStatusrsquoPostSTATUS PUBLISHED) to get the string representation of the published status

32 Creating and Updating Posts

With the Post model ready we need to fine-tune the actions and views for the controllerPostController In this section we first customize the access control of CRUD operationswe then modify the code implementing the create and update operations

321 Customizing Access Control

The first thing we want to do is to customize the access control because the code generatedby yiic does not fit our needs

We modify the accessRules() method in the file wwwrootblogprotectedcontrollers

PostControllerphp as follows

public function accessRules()

return array(

array(rsquoallowrsquo allow all users to perform rsquolistrsquo and rsquoshowrsquo actions

rsquoactionsrsquo=gtarray(rsquoindexrsquo rsquoviewrsquo)

rsquousersrsquo=gtarray(rsquorsquo)

)

array(rsquoallowrsquo allow authenticated users to perform any action

rsquousersrsquo=gtarray(rsquorsquo)

)

array(rsquodenyrsquo deny all users

rsquousersrsquo=gtarray(rsquorsquo)

)

)

The above rules state that all users can access the index and view actions and authenti-cated users can access any actions including the admin action The user should be denied

24 3 Post Management

access in any other scenario Note that these rules are evaluated in the order they arelisted here The first rule matching the current context makes the access decision Forexample if the current user is the system owner who tries to visit the post creation pagethe second rule will match and it will give the access to the user

322 Customizing create and update Operations

The create and update operations are very similar They both need to display an HTMLform to collect user inputs validate them and save them into database The main differ-ence is that the update operation will pre-populate the form with the existing post datafound in the database For this reason the yiic tool generates a partial view wwwroot

blogprotectedviewspost formphp that is embedded in both the create and update

views to render the needed HTML form

We first change the formphp file so that the HTML form only collects the inputs wewant title content tags and status We use plain text fields to collect inputs for thefirst three attributes and a dropdown list to collect input for status The dropdown listoptions are the text displays of the possible post statuses

ltphp echo CHtmlactiveDropDownList($postrsquostatusrsquoLookupitems(rsquoPostStatusrsquo)) gt

In the above we call Lookupitems(rsquoPostStatusrsquo) to bring back the list of post statuses

We then modify the Post class so that it can automatically set some attributes (eg create

time author id) before a post is saved to the database We override the beforeSave()

method as follows

protected function beforeSave()

if(parentbeforeSave())

if($this-gtisNewRecord)

$this-gtcreate time=$this-gtupdate time=time()

$this-gtauthor id=Yiiapp()-gtuser-gtid

else

$this-gtupdate time=time()

return true

else

return false

33 Displaying Posts 25

When we save a post we want to update the tbl tag table to reflect the change of tagfrequencies We can do this work in the afterSave() method which is automaticallyinvoked by Yii after a post is successfully saved into the database

protected function afterSave()

parentafterSave()

Tagmodel()-gtupdateFrequency($this-gt oldTags $this-gttags)

private $ oldTags

protected function afterFind()

parentafterFind()

$this-gt oldTags=$this-gttags

In the implementation because we want to detect if the user changes the tags in case he isupdating an existing post we need to know what the old tags are For this reason we alsowrite the afterFind() method to keep the old tags in the variable oldTags The methodafterFind() is invoked automatically by Yii when an AR record is populated with thedata from database

We are not going to give details of the TagupdateFrequency() method here Interestedreaders may refer to the file wwwrootyiidemosblogprotectedmodelsTagphp

33 Displaying Posts

In our blog application a post may be displayed among a list of posts or by itself Theformer is implemented as the index operation while the latter the view operation In thissection we customize both operations to fulfill our initial requirements

331 Customizing view Operation

The view operation is implemented by the actionView() method in PostController Itsdisplay is generated by the view view with the view file wwwrootblogprotectedviews

postviewphp

Below is the relevant code implementing the view operation in PostController

public function actionView()

26 3 Post Management

$this-gtrender(rsquoviewrsquoarray(

$post=$this-gtloadModel()

$this-gtrender(rsquoviewrsquoarray(

rsquomodelrsquo=gt$post

))

))

private $ model

public function loadModel()

if($this-gt model===null)

if(isset($ GET[rsquoidrsquo]))

if(Yiiapp()-gtuser-gtisGuest)

$condition=rsquostatus=rsquoPostSTATUS PUBLISHED

rsquo OR status=rsquoPOSTSTATUS ARCHIVED

else

$condition=rsquorsquo

$this-gt model=Postmodel()-gtfindbyPk($ GET[rsquoidrsquo] $condition)

if($this-gt model===null)

throw new CHttpException(404rsquoThe requested page does not existrsquo)

return $this-gt model

Our change mainly lies in the loadModel() method In this method we query the Post

table according to the id GET parameter If the post is not found or if it is not publishedor archived (when the user is a guest) we will throw a 404 HTTP error Otherwise thepost object is returned to actionView() which in turn passes the post object to the viewscript for further display

Tip Yii captures HTTP exceptions (instances of CHttpException) and displaysthem in either predefined templates or customized error views The skeleton appli-cation generated by yiic already contains a customized error view in wwwroot

blogprotectedviewssiteerrorphp We can modify this file if we want tofurther customize the error display

The change in the view script is mainly about ajdusting the formatting and styles of thepost display We will not go into details here Interested readers may refer to wwwroot

blogprotectedviewspostviewphp

33 Displaying Posts 27

332 Customizing index Operation

Like the view operation we customize the index operation in two places the actionIndex()

method in PostController and the view file wwwrootblogprotectedviewspostindex

php We mainly need to add the support for displaying a list of posts that are associatedwith a specified tag

Below is the modified actionIndex() method in PostController

public function actionIndex()

$criteria=new CDbCriteria(array(

rsquoconditionrsquo=gtrsquostatus=rsquoPostSTATUS PUBLISHED

rsquoorderrsquo=gtrsquoupdate time DESCrsquo

rsquowithrsquo=gtrsquocommentCountrsquo

))

if(isset($ GET[rsquotagrsquo]))

$criteria-gtaddSearchCondition(rsquotagsrsquo$ GET[rsquotagrsquo])

$dataProvider=new CActiveDataProvider(rsquoPostrsquo array(

rsquopaginationrsquo=gtarray(

rsquopageSizersquo=gtYiiapp()-gtparams[rsquopostsPerPagersquo]

)

rsquocriteriarsquo=gt$criteria

))

$this-gtrender(rsquoindexrsquoarray(

rsquodataProviderrsquo=gt$dataProvider

))

In the above we first create a query criteria for retrieving post list The criteria statesthat only published posts should be returned and they should be sorted according to theirupdate time in descending order Because when displaying a post in the list we want toshow how many comments the post has received in the criteria we also specify to bringback commentCount which if you remember is a relation declared in Postrelations()

In case when a user wants to see posts with a specific tag we would add a search conditionto the criteria to look for the specified tag

Using the query criteria we create a data provider which mainly serves for three purposesFirst it does pagination of the data when too many results may be returned Herewe customize the pagination by setting the page size to be an application parameterpostsPerPage Second it does sorting according to the user request And finally it feedsthe paginated and sorted data to widgets or view code for presentation

28 3 Post Management

After we finish with actionIndex() we modify the index view as follows Our change ismainly about adding the h1 header when the user specifies to display posts with a tag

ltphp if(empty($ GET[rsquotagrsquo])) gt

lth1gtPosts Tagged with ltigtltphp echo CHtmlencode($ GET[rsquotagrsquo]) gtltigtlth1gt

ltphp endif gt

ltphp $this-gtwidget(rsquoziiwidgetsCListViewrsquo array(

rsquodataProviderrsquo=gt$dataProvider

rsquoitemViewrsquo=gtrsquo viewrsquo

rsquotemplatersquo=gtitemsnpager)) gt

Note that in the above we use [CListView] to display the post list This widget requires apartial view to display the detail of each individual post Here we specify the partial viewto be view which means the file wwwrootblogprotectedviewspost viewphp In thisview script we can acccess the post instance being displayed via a local variable named$data

34 Managing Posts

Managing posts mainly refers to listing posts in an administrative view that allows us tosee posts with all statuses updating them and deleting them They are accomplished bythe admin operation and the delete operation respectively The code generated by yiic

does not need much modification Below we mainly explain how these two operations areimplemented

341 Listing Posts in Tabular View

The admin operation shows posts with all statuses in a tabular view The view supportssorting and pagination The following is the actionAdmin() method in PostController

public function actionAdmin()

$dataProvider=new CActiveDataProvider(rsquoPostrsquo array(

rsquosortrsquo=gtarray(

rsquodefaultOrderrsquo=gtrsquostatus update time DESCrsquo

)

))

$this-gtrender(rsquoadminrsquoarray(

rsquodataProviderrsquo=gt$dataProvider

))

34 Managing Posts 29

The above code is very similar to that in actionIndex() The main difference is in thequery criteria Here we do not specify any particular query criteria since we want to seeall posts We do customize the sort property of the data provider so that by default theposts are shown in the order of their statuses and update time

Below is the code for the admin view

lth1gtManage Postslth1gt

ltphp $this-gtwidget(rsquoziiwidgetsgridCGridViewrsquo array(

rsquodataProviderrsquo=gt$dataProvider

rsquocolumnsrsquo=gtarray(

array(

rsquonamersquo=gtrsquotitlersquo

rsquotypersquo=gtrsquorawrsquo

rsquovaluersquo=gtrsquoCHtmllink(CHtmlencode($data-gttitle) $data-gturl)rsquo

)

array(

rsquonamersquo=gtrsquostatusrsquo

rsquovaluersquo=gtrsquoLookupitem(PostStatus$data-gtstatus)rsquo

)

rsquocreate timedatetimersquo

rsquoupdate timedatetimersquo

array(

rsquoclassrsquo=gtrsquoCButtonColumnrsquo

)

)

)) gt

We use [CGridView] to display the posts It allows us to sort by a column and paginatethrough the posts if there are too many to be displayed in a single page Our change ismainly about how to display each column For example for the title column we specifythat it should be displayed as a hyperlink that points to the detailed view of the post

Tip When displaying text we call CHtmlencode() to encode HTML entities init This prevents from cross-site scripting attack

342 Deleting Posts

In the admin data grid there is a delete button in each row Clicking on the button shoulddelete the corresponding post Internally this triggers the delete action implemented asfollows

30 3 Post Management

public function actionDelete()

if(Yiiapp()-gtrequest-gtisPostRequest)

we only allow deletion via POST request

$this-gtloadModel()-gtdelete()

if(isset($ POST[rsquoajaxrsquo]))

$this-gtredirect(array(rsquoindexrsquo))

else

throw new CHttpException(400rsquoInvalid request Please do not repeat this request againrsquo)

The above code is the one generated by the yiic tool without any change We wouldlike to explain a little bit more about the checking on $ POST[rsquoajaxrsquo] The [CGridView]widget has a very nice feature that its sorting pagination and deletion operations are alldone in AJAX mode by default That means the whole page does not get reloaded if anyof the above operations is performed However it is also possible that the widget runs innon-AJAX mode (by setting its ajaxUpdate property to be false or disabling JavaScript onthe client side) It is necessary for the delete action to differentiate these two scenarios ifthe delete request is made via AJAX we should not redirect the user browser otherwisewe should

Deleting a post should also cause the deletion of all comments for that post In additionwe should also update the tbl tag table regarding the tags for the deleted post Both ofthese tasks can be achieved by writing an afterDelete method in the Post model class asfollows

protected function afterDelete()

parentafterDelete()

Commentmodel()-gtdeleteAll(rsquopost id=rsquo$this-gtid)

Tagmodel()-gtupdateFrequency($this-gttags rsquorsquo)

The above code is very straightforward it first deletes all those comments whose post id

is the same as the ID of the deleted post it then updates the tbl tag table for the tags

of the deleted post

34 Managing Posts 31

Tip We have to explicitly delete all comments for the deleted post here becauseSQLite does not really support foreign key constraints In a DBMS that supportsthis constraint (such as MySQL PostgreSQL) the foreign key constraint can be setup such that the DBMS automatically deletes the related comments if the post isdeleted In that case we no longer this explicit deletion call in our code

32 3 Post Management

Chapter 4

Comment Management

41 Customizing Comment Model

For the Comment model we mainly need to customize the rules() and attributeLabels()

methods The attributeLabels() method returns a mapping between attribute namesand attribute labels We do not need to touch relations() since the code generated bythe yiic tool is good enough

411 Customizing rules() Method

We first customize the validation rules generated by the yiic tool The following rules areused for comments

public function rules()

return array(

array(rsquocontent author emailrsquo rsquorequiredrsquo)

array(rsquoauthor email urlrsquo rsquolengthrsquo rsquomaxrsquo=gt128)

array(rsquoemailrsquorsquoemailrsquo)

array(rsquourlrsquorsquourlrsquo)

)

In the above we specify that the author email and content attributes are required thelength of author email and url cannot exceed 128 the email attribute must be a validemail address and the url attribute must be a valid URL

412 Customizing attributeLabels() Method

We then customize the attributeLabels() method to declare the label display for eachmodel attribute This method returns an array consisting of name-label pairs When wecall CHtmlactiveLabel() to display an attribute label

34 4 Comment Management

public function attributeLabels()

return array(

rsquoidrsquo =gt rsquoIdrsquo

rsquocontentrsquo =gt rsquoCommentrsquo

rsquostatusrsquo =gt rsquoStatusrsquo

rsquocreate timersquo =gt rsquoCreate Timersquo

rsquoauthorrsquo =gt rsquoNamersquo

rsquoemailrsquo =gt rsquoEmailrsquo

rsquourlrsquo =gt rsquoWebsitersquo

rsquopost idrsquo =gt rsquoPostrsquo

)

Tip If the label for an attribute is not declared in attributeLabels() an algo-rithm will be used to generate an appropriate label For example a label VerifyCode will be generated for attributes verify code or verifyCode

413 Customizing Saving Process

Because we want to record the creation time of a comment we override the beforeSave()

method of Comment like we do for the Post model

protected function beforeSave()

if(parentbeforeSave())

if($this-gtisNewRecord)

$this-gtcreate time=time()

return true

else

return false

42 Creating and Displaying Comments

In this section we implement the comment display and creation features

421 Displaying Comments

Instead of displaying and creating comments on individual pages we use the post detailpage (generated by the view action of PostController) Below the post content display

42 Creating and Displaying Comments 35

we display first a list of comments belonging to that post and then a comment creationform

In order to display comments on the post detail page we modify the view script wwwrootblogprotectedviewspostviewphp as follows

ltdiv id=commentsgt

ltphp if($model-gtcommentCountgt=1) gt

lth3gt

ltphp echo $model-gtcommentCount rsquocomment(s)rsquo gt

lth3gt

ltphp $this-gtrenderPartial(rsquo commentsrsquoarray(

rsquopostrsquo=gt$model

rsquocommentsrsquo=gt$model-gtcomments

)) gt

ltphp endif gt

ltdivgt

In the above we call renderPartial() to render a partial view named comments to displaythe list of comments belonging to the current post Note that in the view we use theexpression $model-gtcomments to retrieve the comments for the post This is valid becausewe have declared a comments relation in the Post class Evaluating this expression wouldtrigger an implicit JOIN database query to bring back the proper comments This featureis known as lazy relational query

The partial view comments is not very interesting It mainly goes through every commentand displays the detail of it Interested readers may refer to wwwrootyiidemosblog

protectedpost commentsphp

422 Creating Comments

To handle comment creation we first modify the actionView() method of PostController

as follows

public function actionView()

$post=$this-gtloadModel()

$comment=$this-gtnewComment($post)

$this-gtrender(rsquoviewrsquoarray(

rsquomodelrsquo=gt$post

rsquocommentrsquo=gt$comment

))

36 4 Comment Management

protected function newComment($post)

$comment=new Comment

if(isset($ POST[rsquoCommentrsquo]))

$comment-gtattributes=$ POST[rsquoCommentrsquo]

if($post-gtaddComment($comment))

if($comment-gtstatus==CommentSTATUS PENDING)

Yiiapp()-gtuser-gtsetFlash(rsquocommentSubmittedrsquorsquoThank yoursquo)

$this-gtrefresh()

return $comment

In the above we call the newComment() method before we render view In the newComment()

method we generate a Comment instance and check if the comment form is submitted If sowe try to add the comment for the post by calling $post-gtaddComment($comment) If it goesthrough we refresh the post detail page In case the comment needs to be approved we willshow a flash message to indicate this decision A flash message is usually a confirmationmessage displayed to end users If the user clicks on the refresh button of his browser themessage will disappear

We also need to modify wwwrootblogprotectedviewspostviewphp furthermore

ltdiv id=commentsgt

lth3gtLeave a Commentlth3gt

ltphp if(Yiiapp()-gtuser-gthasFlash(rsquocommentSubmittedrsquo)) gt

ltdiv class=successgt

ltphp echo Yiiapp()-gtuser-gtgetFlash(rsquocommentSubmittedrsquo) gt

ltdivgt

ltphp else gt

ltphp $this-gtrenderPartial(rsquocomment formrsquoarray(

rsquomodelrsquo=gt$comment

)) gt

ltphp endif gt

ltdivgtlt-- comments --gt

43 Managing Comments 37

In the above code we display the flash message if it is available If not we displaythe comment input form by rendering the partial view wwwrootblogprotectedviews

comment formphp

43 Managing Comments

Comment management includes updating deleting and approving comments These op-erations are implemented as actions in the CommentController class

431 Updating and Deleting Comments

The code generated by yiic for updating and deleting comments remains largely un-changed

432 Approving Comments

When comments are newly created they are in pending approval status and need to beapproved in order to be visible to guest users Approving a comment is mainly aboutchanging the status column of the comment

We create an actionApprove() method in CommentController as follows

public function actionApprove()

if(Yiiapp()-gtrequest-gtisPostRequest)

$comment=$this-gtloadModel()

$comment-gtapprove()

$this-gtredirect(array(rsquoindexrsquo))

else

throw new CHttpException(400rsquoInvalid requestrsquo)

In the above when the approve action is invoked via a POST request we call the approve()

method defined in the Comment model to change the status We then redirect the userbrowser to the page displaying the post that this comment belongs to

We also modify the actionIndex() method of Comment to show all comments We wouldlike to see comments pending approved to show up first

public function actionIndex()

38 4 Comment Management

$dataProvider=new CActiveDataProvider(rsquoCommentrsquo array(

rsquocriteriarsquo=gtarray(

rsquowithrsquo=gtrsquopostrsquo

rsquoorderrsquo=gtrsquotstatus tcreate time DESCrsquo

)

))

$this-gtrender(rsquoindexrsquoarray(

rsquodataProviderrsquo=gt$dataProvider

))

Notice that in the above code because both tbl post and tbl comment have columns statusand create time we need to disambiguate the corresponding column reference by prefixingthem with table alias names As described in the guide the alias for the primary table ina relational query is always t Therefore we are prefixing t to the status and create time

columns in the above code

Like the post index view the index view for CommentController uses [CListView] to displaythe comment list which in turn uses the partial view wwwrootblogprotectedviews

comment viewphp to display the detail of each individual comment We will not go intodetails here Interested readers may refer to the corresponding file in the blog demowwwrootyiidemosblogprotectedviewscomment viewphp

Chapter 5

Portlets

51 Customizing Page Layout

Features like rdquothe most recent commentsrdquo rdquotag cloudrdquo are better to be implemented inportlets A portlet is a pluggable user interface component that renders a fragment ofHTML code In this section we describe how to set up the portlet architecture for ourblog application

Before we go into details about developing portlets we first need to adjust our page layoutso that the portlets can be placed in a side bar

A page layout determines how to share common sections (eg header footer) amongdifferent pages and how to embed contents that are specific for different pages In Yiia page layout is represented as a view under the protectedviewslayouts folder Whenwe call the render() method in a controller the rendering result will be automaticallyembedded into this layout view before it is displayed to end users The default layoutview is named as main which means the wwwrootblogprotectedviewslayoutsmain

php file in our blog application

We could directly modify the main layout to add a side bar However doing so wouldmake some irrelevant pages such as about contact pages to have a side bar which is notalways desirable For this reason we need different layouts for different pages

We thus create two different layouts column1 and column2 The former displays the pagein a single column while the latter displays two columns in the page one of which is theside bar We will use column2 layout for the post and comment pages and column1 forthe rest of the pages Because both column1 and column2 still share the same header andfooter sections we let them to share the main layout

Based on the above description we create the column1 layout as follows

ltphp $this-gtbeginContent(rsquolayoutsmainrsquo) gt

ltdiv class=containergt

40 5 Portlets

ltdiv id=contentgt

ltphp echo $content gt

ltdivgtlt-- content --gt

ltdivgt

ltphp $this-gtendContent() gt

and the column2 layout as follows

ltphp $this-gtbeginContent(rsquolayoutsmainrsquo) gt

ltdiv class=containergt

ltdiv class=span-18gt

ltdiv id=contentgt

ltphp echo $content gt

ltdivgtlt-- content --gt

ltdivgt

ltdiv class=span-6 lastgt

ltdiv id=sidebargt

portlets to be placed here

ltdivgtlt-- sidebar --gt

ltdivgt

ltdivgt

ltphp $this-gtendContent() gt

As we can see both column1 and column2 are enclosed in a pair of beginContent andendContent calls They essentially use the specified view layoutsmain to decorate theenclosed content In all these layout views the variable $content refers to the dynamiccontent to be embedded into the layout

Besides customizing the layout view file we also need to adjust the CSS file wwwroot

blogcssmaincss so that the overall appearance would look like what we see in the blogdemo We will not go into details here

Info The skeleton application generated by the yiic tool uses the BluePrint CSSframework It provides a nice font reset and a robust grid system For more detailson how to use this framework please refer to its documentation

After creating the new layouts we need to set the layout property of controllers to makeuse of them So far we have three controllers SiteController PostController andCommentController We set SiteController to use column1 as the default layout whilethe rest two of the controllers use column2 for example

class PostController extends Controller

52 Creating User Menu Portlet 41

public $layout=rsquocolumn2rsquo

Tip If some view of the controller does not use the same layout as declaredby the layout property it may customize it in the view by a simple assignment$this-gtlayout=rsquonewlayoutrsquo

52 Creating User Menu Portlet

Based on the requirements analysis we need three different portlets the rdquouser menurdquoportlet the rdquotag cloudrdquo portlet and the rdquorecent commentsrdquo portlet We will develop theseportlets by extending the [CPortlet] widget provided by Yii

In this section we will develop our first concrete portlet - the user menu portlet whichdisplays a list of menu items that are only available to authenticated users The menucontains four items

bull Approve Comments a hyperlink that leads to a list of comments pending approval

bull Create New Post a hyperlink that leads to the post creation page

bull Manage Posts a hyperlink that leads to the post management page

bull Logout a link button that would log out the current user

521 Creating UserMenu Class

We create the UserMenu class to represent the logic part of the user menu portlet Theclass is saved in the file wwwrootblogprotectedcomponentsUserMenuphp which has thefollowing content

Yiiimport(rsquoziiwidgetsCPortletrsquo)

class UserMenu extends CPortlet

public function init()

$this-gttitle=CHtmlencode(Yiiapp()-gtuser-gtname)

parentinit()

42 5 Portlets

protected function renderContent()

$this-gtrender(rsquouserMenursquo)

The UserMenu class extends from the CPortlet class that we created previously It overridesboth the init() method and the renderContent() method of CPortlet The former setsthe portlet title to be the name of the current user the latter generates the portlet bodycontent by rendering a view named userMenu

Tip Notice that we have to explicitly include the CPortlet class by calling Yii

import() before we refer to it the first time This is because CPortlet is part of thezii project ndash the official extension library for Yii For performance considerationclasses in this project are not listed as core classes Therefore we have to import itbefore we use it the first time

522 Creating userMenu View

Next we create the userMenu view which is saved in the file wwwrootblogprotected

componentsviewsuserMenuphp

ltulgt

ltligtltphp echo CHtmllink(rsquoCreate New Postrsquoarray(rsquopostcreatersquo)) gtltligt

ltligtltphp echo CHtmllink(rsquoManage Postsrsquoarray(rsquopostadminrsquo)) gtltligt

ltligtltphp echo CHtmllink(rsquoApprove Commentsrsquoarray(rsquocommentindexrsquo))

rsquo (rsquo Commentmodel()-gtpendingCommentCount rsquo)rsquo gtltligt

ltligtltphp echo CHtmllink(rsquoLogoutrsquoarray(rsquositelogoutrsquo)) gtltligt

ltulgt

Info By default view files for a widget should be placed under the views sub-directory of the directory containing the widget class file The file name must bethe same as the view name

523 Using UserMenu Portlet

It is time for us to make use of our newly completed UserMenu portlet We modify thelayout view file wwwrootblogprotectedviewslayoutscolumn2php as follows

53 Creating Tag Cloud Portlet 43

ltdiv id=sidebargt

ltphp if(Yiiapp()-gtuser-gtisGuest) $this-gtwidget(rsquoUserMenursquo) gt

ltdivgt

In the above we call the widget() method to generate and execute an instance of theUserMenu class Because the portlet should only be displayed to authenticated users weonly call widget() when the isGuest property of the current user is false (meaning theuser is authenticated)

524 Testing UserMenu Portlet

Letrsquos test what we have so far

1 Open a browser window and enter the URL httpwwwexamplecomblogindex

php Verify that there is nothing displayed in the side bar section of the page

2 Click on the Login hyperlink and fill out the login form to login If successful verifythat the UserMenu portlet appears in the side bar and the portlet has the usernameas its title

3 Click on the rsquoLogoutrsquo hyperlink in the UserMenu portlet Verify that the logout actionis successful and the UserMenu portlet disappears

525 Summary

What we have created is a portlet that is highly reusable We can easily reuse it in adifferent project with little or no modification Moreover the design of this portlet followsclosely the philosophy that logic and presentation should be separated While we did notpoint this out in the previous sections such practice is used nearly everywhere in a typicalYii application

53 Creating Tag Cloud Portlet

Tag cloud displays a list of post tags with visual decorations hinting the popularity of eachindividual tag

531 Creating TagCloud Class

We create the TagCloud class in the file wwwrootblogprotectedcomponentsTagCloud

php The file has the following content

44 5 Portlets

Yiiimport(rsquoziiwidgetsCPortletrsquo)

class TagCloud extends CPortlet

public $title=rsquoTagsrsquo

public $maxTags=20

protected function renderContent()

$tags=Tagmodel()-gtfindTagWeights($this-gtmaxTags)

foreach($tags as $tag=gt$weight)

$link=CHtmllink(CHtmlencode($tag) array(rsquopostindexrsquorsquotagrsquo=gt$tag))

echo CHtmltag(rsquospanrsquo array(

rsquoclassrsquo=gtrsquotagrsquo

rsquostylersquo=gtfont-size$weightpt) $link)n

Unlike the UserMenu portlet the TagCloud portlet does not use a view Instead its presen-tation is done in the renderContent() method This is because the presentation does notcontain much HTML tags

We display each tag as a hyperlink to the post index page with the corresponding tagparameter The font size of each tag link is adjusted according to their relative weightamong other tags If a tag has higher frequency value than the other it will have a biggerfont size

532 Using TagCloud Portlet

Usage of the TagCloud portlet is very simple We modify the layout file wwwrootblog

protectedviewslayoutsmainphp as follows

ltdiv id=sidebargt

ltphp if(Yiiapp()-gtuser-gtisGuest) $this-gtwidget(rsquoUserMenursquo) gt

ltphp $this-gtwidget(rsquoTagCloudrsquo array(

rsquomaxTagsrsquo=gtYiiapp()-gtparams[rsquotagCloudCountrsquo]

)) gt

ltdivgt

54 Creating Recent Comments Portlet 45

54 Creating Recent Comments Portlet

In this section we create the last portlet that displays a list of comments recently pub-lished

541 Creating RecentComments Class

We create the RecentComments class in the file wwwrootblogprotectedcomponentsRecentCommentsphp The file has the following content

Yiiimport(rsquoziiwidgetsCPortletrsquo)

class RecentComments extends CPortlet

public $title=rsquoRecent Commentsrsquo

public $maxComments=10

public function getRecentComments()

return Commentmodel()-gtfindRecentComments($this-gtmaxComments)

protected function renderContent()

$this-gtrender(rsquorecentCommentsrsquo)

In the above we invoke the findRecentComments method which is defined in the Comment

class as follows

class Comment extends CActiveRecord

public function findRecentComments($limit=10)

return $this-gtwith(rsquopostrsquo)-gtfindAll(array(

rsquoconditionrsquo=gtrsquotstatus=rsquoselfSTATUS APPROVED

rsquoorderrsquo=gtrsquotcreate time DESCrsquo

rsquolimitrsquo=gt$limit

))

46 5 Portlets

542 Creating recentComments View

The recentComments view is saved in the file wwwrootblogprotectedcomponentsviews

recentCommentsphp It simply displays every comment returned by the RecentComments

getRecentComments() method

543 Using RecentComments Portlet

We modify the layout file wwwrootblogprotectedviewslayoutsmainphp to embedthis last portlet

ltdiv id=sidebargt

ltphp if(Yiiapp()-gtuser-gtisGuest) $this-gtwidget(rsquoUserMenursquo) gt

ltphp $this-gtwidget(rsquoTagCloudrsquo array(

rsquomaxTagsrsquo=gtYiiapp()-gtparams[rsquotagCloudCountrsquo]

)) gt

ltphp $this-gtwidget(rsquoRecentCommentsrsquo array(

rsquomaxCommentsrsquo=gtYiiapp()-gtparams[rsquorecentCommentCountrsquo]

)) gt

ltdivgt

Chapter 6

Final Work

61 Beautifying URLs

The URLs linking various pages of our blog application currently look ugly For examplethe URL for the page showing a post looks like the following

indexphpr=postshowampid=1

In this section we describe how to beautify these URLs and make them SEO-friendlyOur goal is to be able to use the following URLs in the application

1 indexphppostsyii leads to the page showing a list of posts with tag yii

2 indexphppost2A+Test+Post leads to the page showing the detail of the postwith ID 2 whose title is A Test Post

3 indexphppostupdate1 leads to the page that allows updating the post with ID1

Note that in the second URL format we include the post title in the URL This is mainlyto make the URL SEO friendly It is said that search engines may also respect the wordsfound in a URL when it is being indexed

To achieve our goal we modify the application configuration as follows

return array(

rsquocomponentsrsquo=gtarray(

rsquourlManagerrsquo=gtarray(

rsquourlFormatrsquo=gtrsquopathrsquo

rsquorulesrsquo=gtarray(

48 6 Final Work

rsquopostltidd+gtlttitlegtrsquo=gtrsquopostviewrsquorsquopostslttaggtrsquo=gtrsquopostindexrsquo

)

)

)

)

In the above we configure the urlManager component by setting its urlFormat propertyto be path and adding a set of rules

The rules are used by urlManager to parse and create the URLs in the desired formatFor example the first rule says that if a URL indexphppostsyii is requested theurlManager component should be responsible to dispatch the request to the route post

index and generate a tag GET parameter with the value yii On the other hand whencreating a URL with the route postindex and parameter tag the urlManager componentwill also use this rule to generate the desired URL indexphppostsyii For this reasonwe say that urlManager is a two-way URL manager

The urlManager component can further beautify our URLs such as hiding indexphp inthe URLs appending suffix like html to the URLs We can obtain these features easilyby configuring various properties of urlManager in the application configuration For moredetails please refer to the Guide

62 Logging Errors

A production Web application often needs sophisticated logging for various events In ourblog application we would like to log the errors occurring when it is being used Sucherrors could be programming mistakes or usersrsquo misuse of the system Logging these errorswill help us to improve the blog application

We enable the error logging by modifying the application configuration as follows

return array(

rsquopreloadrsquo=gtarray(rsquologrsquo)

rsquocomponentsrsquo=gtarray(

rsquologrsquo=gtarray(

rsquoclassrsquo=gtrsquoCLogRouterrsquo

rsquoroutesrsquo=gtarray(

array(

rsquoclassrsquo=gtrsquoCFileLogRoutersquo

rsquolevelsrsquo=gtrsquoerror warningrsquo

63 Final Tune-up and Deployment 49

)

)

)

)

)

With the above configuration if an error or warning occurs detailed information will belogged and saved in a file located under the directory wwwrootblogprotectedruntime

The log component offers more advanced features such as sending log messages to a listof email addresses displaying log messages in JavaScript console window etc For moredetails please refer to the Guide

63 Final Tune-up and Deployment

We are close to finish our blog application Before deployment we would like to do sometune-ups

631 Changing Home Page

We change to use the post list page as the home page We modify the application config-uration as follows

return array(

rsquodefaultControllerrsquo=gtrsquopostrsquo

)

Tip Because PostController already declares list to be its default action whenwe access the home page of the application we will see the result generated by thelist action of the post controller

632 Enabling Schema Caching

Because ActiveRecord relies on the metadata about tables to determine the column infor-mation it takes time to read the metadata and analyze it This may not be a problemduring development stage but for an application running in production mode it is a totalwaste of time if the database schema does not change Therefore we should enable theschema caching by modifying the application configuration as follows

50 6 Final Work

return array(

rsquocomponentsrsquo=gtarray(

rsquocachersquo=gtarray(

rsquoclassrsquo=gtrsquoCDbCachersquo

)

rsquodbrsquo=gtarray(

rsquoclassrsquo=gtrsquosystemdbCDbConnectionrsquo

rsquoconnectionStringrsquo=gtrsquosqlitewwwrootblogprotecteddatablogdbrsquo

rsquoschemaCachingDurationrsquo=gt3600

)

)

)

In the above we first add a cache component which uses a default SQLite database as thecaching storage If our server is equipped with other caching extensions such as APCwe could change to use them as well We also modify the db component by setting itsschemaCachingDuration property to be 3600 which means the parsed database schemadata can remain valid in cache for 3600 seconds

633 Disabling Debugging Mode

We modify the entry script file wwwrootblogindexphp by removing the line definingthe constant YII DEBUG This constant is useful during development stage because it allowsYii to display more debugging information when an error occurs However when theapplication is running in production mode displaying debugging information is not agood idea because it may contain sensitive information such as where the script file islocated and the content in the file etc

634 Deploying the Application

The final deployment process manly involves copying the directory wwwrootblog to thetarget directory The following checklist shows every needed step

1 Install Yii in the target place if it is not available

2 Copy the entire directory wwwrootblog to the target place

3 Edit the entry script file indexphp by pointing the $yii variable to the new Yiibootstrap file

4 Edit the file protectedyiicphp by setting the $yiic variable to be the new Yiiyiicphp file

64 Future Enhancements 51

5 Change the permission of the directories assets and protectedruntime so that theyare writable by the Web server process

64 Future Enhancements

641 Using a Theme

Without writing any code our blog application is already themeable To use a themewe mainly need to develop the theme by writing customized view files in the theme Forexample to use a theme named classic that uses a different page layout we would createa layout view file wwwrootblogthemesclassicviewslayoutsmainphp We also needto change the application configuration to indicate our choice of the classic theme

return array(

rsquothemersquo=gtrsquoclassicrsquo

)

642 Internationalization

We may also internationalize our blog application so that its pages can be displayed indifferent languages This mainly involves efforts in two aspects

First we may create view files in different languages For example for the list pageof PostController we can create a view file wwwrootblogprotectedviewspostzh cn

listphp When the application is configured to use simplified Chinese (the language codeis zh cn) Yii will automatically use this new view file instead of the original one

Second we may create message translations for those messages generated by code Themessage translations should be saved as files under the directory wwwrootblogprotected

messages We also need to modify the code where we use text strings by enclosing themin the method call Yiit()

For more details about internationalization please refer to the Guide

643 Improving Performance with Cache

While the Yii framework itself is very efficient it is not necessarily true that an applicationwritten in Yii efficient There are several places in our blog application that we can improvethe performance For example the tag clould portlet could be one of the performancebottlenecks because it involves complex database query and PHP logic

52 6 Final Work

We can make use of the sophisticated caching feature provided by Yii to improve theperformance One of the most useful components in Yii is COutputCache which caches afragment of page display so that the underlying code generating the fragment does not needto be executed for every request For example in the layout file wwwrootblogprotected

viewslayoutsmainphp we can enclose the tag cloud portlet with COutputCache

ltphp if($this-gtbeginCache(rsquotagCloudrsquo array(rsquodurationrsquo=gt3600))) gt

ltphp $this-gtwidget(rsquoTagCloudrsquo array(

rsquomaxTagsrsquo=gtYiiapp()-gtparams[rsquotagCloudCountrsquo]

)) gt

ltphp $this-gtendCache() gt

With the above code the tag cloud display will be served from cache instead of beinggenerated on-the-fly for every request The cached content will remain valid in cache for3600 seconds

644 Adding New Features

Our blog application only has very basic functionalities To become a complete blogsystem more features are needed for example calendar portlet email notifications postcategorization archived post portlet and so on We will leave the implementation of thesefeatures to interested readers

  • Contents
  • License
  • Getting Started
    • Building a Blog System using Yii
    • Testdriving with Yii
      • Installing Yii
      • Creating Skeleton Application
      • Application Workflow
        • Requirements Analysis
        • Overall Design
          • Initial Prototyping
            • Setting Up Database
              • Creating Database
              • Establishing Database Connection
                • Scaffolding
                • Authenticating User
                • Summary
                  • Post Management
                    • Customizing Post Model
                      • Customizing rules() Method
                      • Customizing relations() Method
                      • Representing Status in Text
                        • Creating and Updating Posts
                          • Customizing Access Control
                          • Customizing create and update Operations
                            • Displaying Posts
                              • Customizing view Operation
                              • Customizing index Operation
                                • Managing Posts
                                  • Listing Posts in Tabular View
                                  • Deleting Posts
                                      • Comment Management
                                        • Customizing Comment Model
                                          • Customizing rules() Method
                                          • Customizing attributeLabels() Method
                                          • Customizing Saving Process
                                            • Creating and Displaying Comments
                                              • Displaying Comments
                                              • Creating Comments
                                                • Managing Comments
                                                  • Updating and Deleting Comments
                                                  • Approving Comments
                                                      • Portlets
                                                        • Customizing Page Layout
                                                        • Creating User Menu Portlet
                                                          • Creating UserMenu Class
                                                          • Creating userMenu View
                                                          • Using UserMenu Portlet
                                                          • Testing UserMenu Portlet
                                                          • Summary
                                                            • Creating Tag Cloud Portlet
                                                              • Creating TagCloud Class
                                                              • Using TagCloud Portlet
                                                                • Creating Recent Comments Portlet
                                                                  • Creating RecentComments Class
                                                                  • Creating recentComments View
                                                                  • Using RecentComments Portlet
                                                                      • Final Work
                                                                        • Beautifying URLs
                                                                        • Logging Errors
                                                                        • Final Tune-up and Deployment
                                                                          • Changing Home Page
                                                                          • Enabling Schema Caching
                                                                          • Disabling Debugging Mode
                                                                          • Deploying the Application
                                                                            • Future Enhancements
                                                                              • Using a Theme
                                                                              • Internationalization
                                                                              • Improving Performance with Cache
                                                                              • Adding New Features
Page 2: Yii Blog 1.1.0 Demo Tutor

Contents

Contents i

License v

1 Getting Started 1

11 Building a Blog System using Yii 1

12 Testdriving with Yii 1

121 Installing Yii 1

122 Creating Skeleton Application 2

123 Application Workflow 3

13 Requirements Analysis 4

14 Overall Design 5

2 Initial Prototyping 9

21 Setting Up Database 9

211 Creating Database 9

212 Establishing Database Connection 9

22 Scaffolding 11

23 Authenticating User 14

24 Summary 17

3 Post Management 19

ii Contents

31 Customizing Post Model 19

311 Customizing rules() Method 19

312 Customizing relations() Method 20

313 Representing Status in Text 21

32 Creating and Updating Posts 23

321 Customizing Access Control 23

322 Customizing create and update Operations 24

33 Displaying Posts 25

331 Customizing view Operation 25

332 Customizing index Operation 27

34 Managing Posts 28

341 Listing Posts in Tabular View 28

342 Deleting Posts 29

4 Comment Management 33

41 Customizing Comment Model 33

411 Customizing rules() Method 33

412 Customizing attributeLabels() Method 33

413 Customizing Saving Process 34

42 Creating and Displaying Comments 34

421 Displaying Comments 34

422 Creating Comments 35

43 Managing Comments 37

431 Updating and Deleting Comments 37

432 Approving Comments 37

Contents iii

5 Portlets 39

51 Customizing Page Layout 39

52 Creating User Menu Portlet 41

521 Creating UserMenu Class 41

522 Creating userMenu View 42

523 Using UserMenu Portlet 42

524 Testing UserMenu Portlet 43

525 Summary 43

53 Creating Tag Cloud Portlet 43

531 Creating TagCloud Class 43

532 Using TagCloud Portlet 44

54 Creating Recent Comments Portlet 45

541 Creating RecentComments Class 45

542 Creating recentComments View 46

543 Using RecentComments Portlet 46

6 Final Work 47

61 Beautifying URLs 47

62 Logging Errors 48

63 Final Tune-up and Deployment 49

631 Changing Home Page 49

632 Enabling Schema Caching 49

633 Disabling Debugging Mode 50

634 Deploying the Application 50

64 Future Enhancements 51

iv Contents

641 Using a Theme 51

642 Internationalization 51

643 Improving Performance with Cache 51

644 Adding New Features 52

License of Yii

The Yii framework is free software It is released under the terms of the following BSDLicense

Copyright ccopy2008-2009 by Yii Software LLC All rights reserved

Redistribution and use in source and binary forms with or without modification arepermitted provided that the following conditions are met

1 Redistributions of source code must retain the above copyright notice this list ofconditions and the following disclaimer

2 Redistributions in binary form must reproduce the above copyright notice this list ofconditions and the following disclaimer in the documentation andor other materialsprovided with the distribution

3 Neither the name of Yii Software LLC nor the names of its contributors may beused to endorse or promote products derived from this software without specificprior written permission

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS rdquoAS

ISrdquo AND ANY EXPRESS OR IMPLIED WARRANTIES INCLUDING BUT NOT LIMITED TO THE

IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PUR-

POSE ARE DISCLAIMED IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBU-

TORS BE LIABLE FOR ANY DIRECT INDIRECT INCIDENTAL SPECIAL EXEMPLARY OR

CONSEQUENTIAL DAMAGES (INCLUDING BUT NOT LIMITED TO PROCUREMENT OF SUB-

STITUTE GOODS OR SERVICES LOSS OF USE DATA OR PROFITS OR BUSINESS INTERRUP-

TION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY WHETHER IN CONTRACT

STRICT LIABILITY OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY

WAY OUT OF THE USE OF THIS SOFTWARE EVEN IF ADVISED OF THE POSSIBILITY OF

SUCH DAMAGE

vi Contents

Chapter 1

Getting Started

11 Building a Blog System using Yii

This tutorial describes how to use Yii to develop a blog application shown as the blog demowhich can be found in the Yii release files It explains in detail every step to be takenduring the development which may also be applied in developing other Web applicationsAs a complement to the Guide and the Class Reference of Yii this tutorial aims to showpractical usage of Yii instead of thorough and definitive description

Readers of this tutorial are not required to have prior knowledge about Yii However basicknowledge of object-oriented programming (OOP) and database programming would helpreaders to understand the tutorial more easily

This tutorial is released under the Terms of Yii Documentation

12 Testdriving with Yii

In this section we describe how to create a skeleton application that will serve as ourstarting point For simplicity we assume that the document root of our Web server iswwwroot and the corresponding URL is httpwwwexamplecom

121 Installing Yii

We first install the Yii framework Grab a copy of the Yii release file (version 110 orabove) from wwwyiiframeworkcom and unpack it to the directory wwwrootyii Doublecheck to make sure that there is a directory wwwrootyiiframework

Tip The Yii framework can be installed anywhere in the file system not necessarilyunder a Web folder Its framework directory contains all framework code and isthe only framework directory needed when deploying an Yii application A singleinstallation of Yii can be used by multiple Yii applications

2 1 Getting Started

After installing Yii open a browser window and access the URL httpwwwexample

comyiirequirementsindexphp It shows the requirement checker provided in the Yiirelease For our blog application besides the minimal requirements needed by Yii we alsoneed to enable both the pdo and pdo sqlite PHP extensions so that we can access SQLitedatabases

122 Creating Skeleton Application

We then use the yiic tool to create a skeleton application under the directory wwwroot

blog The yiic tool is a command line tool provided in the Yii release It can be used togenerate code to reduce certain repetitive coding tasks

Open a command window and execute the following command

wwwrootyiiframeworkyiic webapp wwwrootblog

Create a Web application under rsquowwwrootblogrsquo [Yes|No]y

Tip In order to use the yiic tool as shown above the CLI PHP program must beon the command search path If not the following command may be used instead

pathtophp wwwrootyiiframeworkyiicphp webapp wwwrootblog

To try out the application we just created open a Web browser and navigate to the URLhttpwwwexamplecomblogindexphp We should see that our skeleton applicationalready has four fully functional pages the homepage the about page the contact pageand the login page

In the following we briefly describe what we have in this skeleton application

Entry Script

We have an entry script file wwwrootblogindexphp which has the following content

ltphp

$yii=rsquowwwrootframeworkyiiphprsquo

$config=dirname( FILE )rsquoprotectedconfigmainphprsquo

remove the following line when in production mode

defined(rsquoYII DEBUGrsquo) or define(rsquoYII DEBUGrsquotrue)

12 Testdriving with Yii 3

require once($yii)

YiicreateWebApplication($config)-gtrun()

This is the only script that Web users can directly access The script first includes theYii bootstrap file yiiphp It then creates an application instance with the specifiedconfiguration and executes the application

Base Application Directory

We also have an application base directory wwwrootblogprotected The majority of ourcode and data will be placed under this directory and it should be protected from beingaccessed by Web users For Apache httpd Web server we place under this directory ahtaccess file with the following content

deny from all

For other Web servers please refer to the corresponding manual on how to protect adirectory from being accessed by Web users

123 Application Workflow

To help understand how Yii works we describe the main workflow in our skeleton appli-cation when a user is accessing its contact page

1 The user requests the URL httpwwwexamplecomblogindexphpr=sitecontact

2 The entry script is executed by the Web server to process the request

3 An application instance is created and configured with initial property values speci-fied in the application configuration file wwwrootblogprotectedconfigmainphp

4 The application resolves the request into a controller and a controller action For thecontact page request it is resolved as the site controller and the contact action (theactionContact method in wwwrootblogprotectedcontrollersSiteControllerphp)

5 The application creates the site controller in terms of a SiteController instanceand then executes it

6 The SiteController instance executes the contact action by calling its actionContact()method

4 1 Getting Started

7 The actionContact method renders a view named contact to the Web user Inter-nally this is achieved by including the view file wwwrootblogprotectedviews

sitecontactphp and embedding the result into the layout file wwwrootblog

protectedviewslayoutsmainphp

13 Requirements Analysis

The blog system that we are going to develop is a single user system The owner of thesystem will be able to perform the following actions

bull Login and logout

bull Create update and delete posts

bull Publish unpublish and archive posts

bull Approve and delete comments

All other users are guest users who can perform the following actions

bull Read posts

bull Create comments

Additional Requirements for this system include

bull The homepage of the system should display a list of the most recent posts

bull If a page contains more than 10 posts they should be displayed in pages

bull The system should display a post together with its comments

bull The system should be able to list posts with a specified tag

bull The system should show a cloud of tags indicating their use frequencies

bull The system should show a list of most recent comments

bull The system should be themeable

bull The system should use SEO-friendly URLs

14 Overall Design 5

14 Overall Design

Based on the analysis of the requirements we decide to use the following database tablesto store the persistent data for our blog application

bull tbl user stores the user information including username and password

bull tbl post stores the blog post information It mainly consists of the following columns

ndash title required title of the post

ndash content required body content of the post which uses the Markdown format

ndash status required status of the post which can be one of following values

lowast 1 meaning the post is in draft and is not visible to public

lowast 2 meaning the post is published to public

lowast 3 meaning the post is outdated and is not visible in the post list (stillaccessible individually though)

ndash tags optional a list of comma-separated words categorizing the post

bull tbl comment stores the post comment information Each comment is associated witha post and mainly consists of the following columns

ndash name required the author name

ndash email required the author email

ndash website optional the author website URL

ndash content required the comment content in plain text format

ndash status required status of the comment which indicates whether the commentis approved (value 2) or not (value 1)

bull tbl tag stores post tag frequency information that is needed to implement the tagcloud feature The table mainly contains the following columns

ndash name required the unique tag name

ndash frequency required the number of times that the tag appears in posts

bull tbl lookup stores generic lookup information It is essentially a map between integervalues and text strings The former is the data representation in our code while thelatter is the corresponding presentation to end users For example we use integer1 to represent the draft post status and string Draft to display this status to endusers This table mainly contains the following columns

6 1 Getting Started

ndash name the textual representation of the data item that is to be displayed to endusers

ndash code the integer representation of the data item

ndash type the type of the data item

ndash position the relative display order of the data item among other items of thesame type

The following entity-relation (ER) diagram shows the table structure and relationshipsabout the above tables

Figure 11 Entity-Relation Diagram of the Blog Database

Complete SQL statements corresponding to the above ER diagram may be found inthe blog demo In our Yii installation they are in the file wwwrootyiidemosblog

protecteddataschemasqlitesql

Info We name all our table names and column names in lower case This is becausedifferent DBMS often have different case-sensitivity treatment and we want to avoidtroubles like this

We also prefix all our tables with tbl This serves for two purposes First the prefixintroduces a namespace to these tables in case when they need to coexist with othertables in the same database which often happens in a shared hosting environmentwhere a single database is being used by multiple applications Second using tableprefix reduces the possibility of having some table names that are reserved keywordsin DBMS

14 Overall Design 7

We divide the development of our blog application into the following milestones

bull Milestone 1 creating a prototype of the blog system It should consist of most ofthe required functionalities

bull Milestone 2 completing post management It includes creating listing showingupdating and deleting posts

bull Milestone 3 completing comment management It includes creating listing ap-proving updating and deleting post comments

bull Milestone 4 implementing portlets It includes user menu login tag cloud andrecent comments portlets

bull Milestone 5 final tune-up and deployment

8 1 Getting Started

Chapter 2

Initial Prototyping

21 Setting Up Database

Having created a skeleton application and finished the database design in this section wewill create the blog database and establish the connection to it in the skeleton application

211 Creating Database

We choose to create a SQLite database Because the database support in Yii is built on topof PDO we can easily switch to use a different type of DBMS (eg MySQL PostgreSQL)without the need to change our application code

We create the database file blogdb under the directory wwwrootblogprotecteddataNote that both the directory and the database file have to be writable by the Web serverprocess as required by SQLite We may simply copy the database file from the blogdemo in our Yii installation which is located at wwwrootyiidemosblogprotecteddatablogdb We may also generate the database by executing the SQL statements in the filewwwrootyiidemosblogprotecteddataschemasqlitesql

Tip To execute SQL statements we may use the sqlite3 command line tool thatcan be found in the SQLite official website

212 Establishing Database Connection

To use the blog database in the skeleton application we created we need to modify itsapplication configuration which is stored in the PHP script wwwrootblogprotected

configmainphp The script returns an associative array consisting of name-value pairseach of which is used to initialize a writable property of the application instance

We configure the db component as follows

10 2 Initial Prototyping

return array(

rsquocomponentsrsquo=gtarray(

rsquodbrsquo=gtarray(

rsquoconnectionStringrsquo=gtrsquosqlitewwwrootblogprotecteddatablogdbrsquo

rsquotablePrefixrsquo=gtrsquotbl rsquo

)

)

)

The above configuration says that we have a db application component whose connectionStringproperty should be initialized as sqlitewwwrootblogprotecteddatablogdb and whosetablePrefix property should be tbl

With this configuration we can access the DB connection object using Yiiapp()-gtdb

at any place in our code Note that Yiiapp() returns the application instance that wecreate in the entry script If you are interested in possible methods and properties that theDB connection has you may refer to its class reference However in most cases we are notgoing to use this DB connection directly Instead we will use the so-called ActiveRecordto access the database

We would like to explain a bit more about the tablePrefix property that we set in theconfiguration This tells the db connection that it should respect the fact we are usingtbl as the prefix to our database table names In particular if in a SQL statement thereis a token enclosed within double curly brackets (eg post then the db connectionshould translate it into a name with the table prefix (eg tbl post) before sending it toDBMS for execution This feature is especially useful if in future we need to modify thetable name prefix without touching our source code For example if we are developing ageneric content management system (CMS) we may exploit this feature so that when itis being installed in a new environment we can allow users to choose a table prefix theylike

22 Scaffolding 11

Tip If you want to use MySQL instead of SQLite to store data you may cre-ate a MySQL database named blog using the SQL statements in wwwrootyii

demosblogprotecteddataschemamysqlsql Then modify the applicationconfiguration as follows

return array(

rsquocomponentsrsquo=gtarray(

rsquodbrsquo=gtarray(

rsquoconnectionStringrsquo =gt rsquomysqlhost=localhostdbname=blogrsquo

rsquoemulatePreparersquo =gt true

rsquousernamersquo =gt rsquorootrsquo

rsquopasswordrsquo =gt rsquorsquo

rsquocharsetrsquo =gt rsquoutf8rsquo

rsquotablePrefixrsquo =gt rsquotbl rsquo

)

)

)

22 Scaffolding

Create read update and delete (CRUD) are the four basic operations of data objects in anapplication Because the task of implementing the CRUD operations is so common whendeveloping Web applications Yii provides a tool to automate this process (also known asscaffolding) In this section we will describe how to use the tool to implement CRUD forposts and comments

Open a command window and run the following commands

wwwrootyiiframeworkyiic shell wwwrootblogindexphp

Yii Interactive Tool v11

Please type rsquohelprsquo for help Type rsquoexitrsquo to quit

gtgt model

gtgt crud Post

gtgt crud Comment

gtgt exit

12 2 Initial Prototyping

Info Some PHP installations may use a different phpini file for command line(CLI) PHP parser As a result when running the above yiic commands youmay encounter errors like rdquoYiiBaseinclude(PDOphp) failed to open streamrdquoor rdquocould not find driverrdquo Please double check your CLI PHP configuration byexecuting the following command

php -r phpinfo()

The result of the above command will show which phpini file is being used andwhich extensions are loaded If a wrong phpini file is used you may use thefollowing command to explicitly specify the correct phpini to use

php -c phpini wwwrootyiiframeworkyiicphp shell wwwrootblogindexphp

The commands above accomplish two tasks First the model command generates a modelclass file for each table in the blog database Second the crud commands generate thecode needed by the CRUD operations for the Post and Comment models

Tip The command model generates a model class for every table in the databaseSometimes we probably donrsquot want to do so (eg the database contains some irrel-evant tables) In this case we can create model classes one by one For exampleto create the User model we can use the command model User The model com-mand also has some more advanced usages For more details execute help model

command

We can test the generated code by accessing the following URLs

httpwwwexamplecomblogindexphpr=post

httpwwwexamplecomblogindexphpr=comment

Notice that the post and comment features implemented by the generated code are com-pletely independent of each other Also when creating a new post or comment we arerequired to enter information such as author id and create time which in real applica-tion should be set by the program Donrsquot worry We will fix these problems in the nextmilestones For now we should be fairly satisfied as this prototype already contains mostfeatures that we need to implement for the blog application

To prepare for the next milestones letrsquos take a closer look at the files generated by theabove commands All the files are generated under wwwrootblogprotected For conve-nience we group them into model files controller files and view files

22 Scaffolding 13

bull model files

ndash modelsUserphp contains the User class that extends from CActiveRecord andcan be used to access the tbl user database table

ndash modelsPostphp contains the Post class that extends from CActiveRecord andcan be used to access the tbl post database table

ndash modelsTagphp contains the Tag class that extends from CActiveRecord andcan be used to access the tbl tag database table

ndash modelsCommentphp contains the Comment class that extends from CActiveRe-cord and can be used to access the tbl comment database table

ndash modelsLookupphp contains the Lookup class that extends from CActiveRecordand can be used to access the tbl lookup database table

bull controller file

ndash controllersPostControllerphp contains the PostController class which is thecontroller in charge of all CRUD operations about posts

ndash controllersCommentControllerphp contains the CommentController class whichis the controller in charge of all CRUD operations about comments

bull view files

ndash viewspostcreatephp is the view file that shows an HTML form to create anew post

ndash viewspostupdatephp is the view file that shows an HTML form to update anexisting post

ndash viewspostviewphp is the view file that displays the detailed information of apost

ndash viewspostindexphp is the view file that displays a list of posts

ndash viewspostadminphp is the view file that displays posts in a table with ad-ministrative commands

ndash viewspost formphp is the partial view file embedded in viewspostcreate

php and viewspostupdatephp It displays the HTML form for collecting postinformation

ndash viewspost viewphp is the partial view file used by viewspostindexphp Itdisplays the brief view of a single post

ndash a similar set of view files are also generated for comment

14 2 Initial Prototyping

In order to understand better how the above files are used we show in the following theworkflow that occurs in the blog application when displaying a list of posts

1 The user requests the URL httpwwwexamplecomblogindexphpr=posts

2 The entry script is executed by the Web server which creates and initializes anapplication instance to handle the request

3 The application creates an instance of PostController and executes it

4 The PostController instance executes the index action by calling its actionIndex()

method Note that index is the default action if the user does not specify an actionto execute in the URL

5 The actionIndex() method queries database to bring back the list of recent posts

6 The actionIndex() method renders the index view with the post data

23 Authenticating User

Our blog application needs to differentiate between the system owner and guest usersTherefore we need to implement the user authentication feature

As you may have found that the skeleton application already provides user authenticationby checking if the username and password are both demo or admin In this section wewill modify the corresponding code so that the authentication is done against the User

database table

User authentication is performed in a class implementing the IUserIdentity interface Theskeleton application uses the UserIdentity class for this purpose The class is stored inthe file wwwrootblogprotectedcomponentsUserIdentityphp

Tip By convention the name of a class file must be the same as the correspondingclass name suffixed with the extension php Following this convention one canrefer to a class using a path alias For example we can refer to the UserIdentity

class with the alias applicationcomponentsUserIdentity Many APIs in Yiican recognize path aliases (eg YiicreateComponent()) and using path aliasesavoids the necessity of embedding absolute file paths in the code The existence ofthe latter often causes trouble when we deploy an application

We modify the UserIdentity class as follows

23 Authenticating User 15

ltphp

class UserIdentity extends CUserIdentity

private $ id

public function authenticate()

$username=strtolower($this-gtusername)

$user=Usermodel()-gtfind(rsquoLOWER(username)=rsquoarray($username))

if($user===null)

$this-gterrorCode=selfERROR USERNAME INVALID

else if($user-gtvalidatePassword($this-gtpassword))

$this-gterrorCode=selfERROR PASSWORD INVALID

else

$this-gt id=$user-gtid

$this-gtusername=$user-gtusername

$this-gterrorCode=selfERROR NONE

return $this-gterrorCode==selfERROR NONE

public function getId()

return $this-gt id

In the authenticate() method we use the User class to look for a row in the User tablewhose username column is the same as the given username in a case-insensitive mannerRemember that the User class was created using the yiic tool in the prior section Becausethe User class extends from CActiveRecord we can exploit the ActiveRecord feature toaccess the User table in an OOP fashion

In order to check if the user has entered a valid password we invoke the validatePassword

method of the User class We need to modify the file wwwrootblogprotectedmodels

Userphp as follows Note that instead of storing the plain password in the database westore the hash result of the password and a randomly generated salt key When validatingthe user-entered password we should compare the hash results instead

class User extends CActiveRecord

public function validatePassword($password)

return $this-gthashPassword($password$this-gtsalt)===$this-gtpassword

16 2 Initial Prototyping

public function hashPassword($password$salt)

return md5($salt$password)

In the UserIdentity class we also override the getId() method which returns the id valueof the user found in the User table The parent implementation would return the usernameinstead Both the username and id properties will be stored in the user session and maybe accessed via Yiiapp()-gtuser from anywhere in our code

Tip In the UserIdentity class we reference the class CUserIdentity without ex-plicitly including the corresponding class file This is because CUserIdentity is acore class provided by the Yii framework Yii will automatically include the classfile for any core class when it is referenced for the first time

We also do the same with the User class This is because the User class file is placedunder the directory wwwrootblogprotectedmodels which has been added tothe PHP include path according to the following lines found in the applicationconfiguration

return array(

rsquoimportrsquo=gtarray(

rsquoapplicationmodelsrsquo

rsquoapplicationcomponentsrsquo

)

)

The above configuration says that any class whose class file is located under either wwwrootblogprotectedmodels or wwwrootblogprotectedcomponents willbe automatically included when the class is referenced for the first time

The UserIdentity class is mainly used by the LoginForm class to authenticate a user basedon the username and password input collected from the login page The following codefragment shows how UserIdentity is used

$identity=new UserIdentity($username$password)

$identity-gtauthenticate()

switch($identity-gterrorCode)

24 Summary 17

case UserIdentityERROR NONE

Yiiapp()-gtuser-gtlogin($identity)

break

Info People often get confused about identity and the user application componentThe former represents a way of performing authentication while the latter is usedto represent the information related with the current user An application can onlyhave one user component but it can have one or several identity classes dependingon what kind of authentication it supports Once authenticated an identity instancemay pass its state information to the user component so that they are globallyaccessible via user

To test the modified UserIdentity class we can browse the URL httpwwwexample

comblogindexphp and try logging in with the username and password that we store inthe User table If we use the database provided by the blog demo we should be able tologin with username demo and password demo Note that this blog system does not providethe user management feature As a result a user cannot change his account or create anew one through the Web interface The user management feature may be considered asa future enhancement to the blog application

24 Summary

We have completed the milestone 1 Letrsquos summarize what we have done so far

1 We identified the requirements to be fulfilled

2 We installed the Yii framework

3 We created a skeleton application

4 We designed and created the blog database

5 We modified the application configuration by adding the database connection

6 We generated the code that implements the basic CRUD operations for both postsand comments

7 We modified the authentication method to check against the tbl user table

18 2 Initial Prototyping

For a new project most of the time will be spent in step 1 and 4 for this first milestone

Although the code generated by the yiic tool implements fully functional CRUD oper-ations for a database table it often needs to be modified in practical applications Forthis reason in the next two milestone our job is to customize the generated CRUD codeabout posts and comments so that it reaches our initial requirements

In general we first modify the model class file by adding appropriate validation rules anddeclaring relational objects We then modify the controller action and view code for eachindividual CRUD operation

Chapter 3

Post Management

31 Customizing Post Model

The Post model class generated by the yiic tool mainly needs to be modified in two places

bull the rules() method specifies the validation rules for the model attributes

bull the relations() method specifies the related objects

Info A model consists of a list of attributes each associated with a column in thecorresponding database table Attributes can be declared explicitly as class membervariables or implicitly without any declaration

311 Customizing rules() Method

We first specify the validation rules which ensure the attribute values entered by usersare correct before they are saved to the database For example the status attribute ofPost should be an integer 1 2 or 3 The yiic tool also generates validation rules for eachmodel However these rules are based on the table column information and may not beappropriate

Based on the requirement analysis we modify the rules() method as follows

public function rules()

return array(

array(rsquotitle content statusrsquo rsquorequiredrsquo)

array(rsquotitlersquo rsquolengthrsquo rsquomaxrsquo=gt128)

array(rsquostatusrsquo rsquoinrsquo rsquorangersquo=gtarray(123))

array(rsquotagsrsquo rsquomatchrsquo rsquopatternrsquo=gtrsquo^[ws]+$rsquorsquomessagersquo=gtrsquoTags can only contain word charactersrsquo)

20 3 Post Management

array(rsquotagsrsquo rsquonormalizeTagsrsquo)

)

In the above we specify that the title content and status attributes are required thelength of title should not exceed 128 the status attribute value should be 1 (draft) 2(published) or 3 (archived) and the tags attribute should only contain word charactersand commas In addition we use normalizeTags to normalize the user-entered tags so thatthe tags are unique and properly separated with commas

The validators such as required length in and match are all built-in validators providedby Yii The normalizeTags validator is a method-based validator that we need to definein the Post class For more information about how to specify validation rules please referto the Guide

public function normalizeTags($attribute$params)

$this-gttags=Tagarray2string(array unique(Tagstring2array($this-gttags)))

The rules declared in this method are executed one by one when we call the validate() orsave() method of the model instance

Note It is very important to remember that attributes appearing in rules() mustbe those to be entered by end users Other attributes such as id and create time

in the Post model which are set by our code or database should not be in rules()For more details please refer to Securing Attribute Assignments

After making these changes we can visit the post creation page again to verify that thenew validation rules are taking effect

312 Customizing relations() Method

Lastly we customize the relations() method to specify the related objects of a post Bydeclaring these related objects in relations() we can exploit the powerful RelationalActiveRecord (RAR) feature to access the related object information of a post such asits author and comments without the need to write complex SQL JOIN statements

We customize the relations() method as follows

31 Customizing Post Model 21

public function relations()

return array(

rsquoauthorrsquo =gt array(selfBELONGS TO rsquoUserrsquo rsquoauthor idrsquo)

rsquocommentsrsquo =gt array(selfHAS MANY rsquoCommentrsquo rsquopost idrsquo

rsquoconditionrsquo=gtrsquocommentsstatus=rsquoCommentSTATUS APPROVED

rsquoorderrsquo=gtrsquocommentscreate time DESCrsquo)

rsquocommentCountrsquo =gt array(selfSTAT rsquoCommentrsquo rsquopost idrsquo

rsquoconditionrsquo=gtrsquostatus=rsquoCommentSTATUS APPROVED)

)

The above relations state that

bull A post belongs to an author whose class is User and the relationship is establishedbased on the author id attribute value of the post

bull A post has many comments whose class is Comment and the relationship is establishedbased on the post id attribute value of the comments These comments should besorted according to their creation time and the comments must be approved

bull The commentCount relation is a bit special as it returns back an aggregation resultwhich is about how many comments the post has

With the above relation declaration we can easily access the author and comments of apost like the following

$author=$post-gtauthor

echo $author-gtusername

$comments=$post-gtcomments

foreach($comments as $comment)

echo $comment-gtcontent

For more details about how to declare and use relations please refer to the Guide

313 Representing Status in Text

Because the status of a post is stored as an integer in the database we need to provide atextual representation so that it is more intuitive when being displayed to end users In alarge system the similar requirement is very common

22 3 Post Management

As a generic solution we use the tbl lookup table to store the mapping between integervalues and textual representations that are needed by other data objects We modify theLookup model class as follows to more easily access the textual data in the table

class Lookup extends CActiveRecord

private static $ items=array()

public static function items($type)

if(isset(self$ items[$type]))

selfloadItems($type)

return self$ items[$type]

public static function item($type$code)

if(isset(self$ items[$type]))

selfloadItems($type)

return isset(self$ items[$type][$code]) self$ items[$type][$code] false

private static function loadItems($type)

self$ items[$type]=array()

$models=selfmodel()-gtfindAll(array(

rsquoconditionrsquo=gtrsquotype=typersquo

rsquoparamsrsquo=gtarray(rsquotypersquo=gt$type)

rsquoorderrsquo=gtrsquopositionrsquo

))

foreach($models as $model)

self$ items[$type][$model-gtcode]=$model-gtname

Our new code mainly provides two static methods Lookupitems() and Lookupitem()The former returns a list of strings belonging to the specified data type while the latterreturns a particular string for the given data type and data value

Our blog database is pre-populated with two lookup types PostStatus and CommentStatusThe former refers to the possible post statuses while the latter the comment statuses

In order to make our code easier to read we also declare a set of constants to representthe status integer values We should use these constants through our code when referringto the corresponding status values

32 Creating and Updating Posts 23

class Post extends CActiveRecord

const STATUS DRAFT=0

const STATUS PUBLISHED=1

const STATUS ARCHIVED=2

Therefore we can call Lookupitems(rsquoPostStatusrsquo) to get the list of possible post statuses(text strings indexed by the corresponding integer values) and call Lookupitem(rsquoPostStatusrsquoPostSTATUS PUBLISHED) to get the string representation of the published status

32 Creating and Updating Posts

With the Post model ready we need to fine-tune the actions and views for the controllerPostController In this section we first customize the access control of CRUD operationswe then modify the code implementing the create and update operations

321 Customizing Access Control

The first thing we want to do is to customize the access control because the code generatedby yiic does not fit our needs

We modify the accessRules() method in the file wwwrootblogprotectedcontrollers

PostControllerphp as follows

public function accessRules()

return array(

array(rsquoallowrsquo allow all users to perform rsquolistrsquo and rsquoshowrsquo actions

rsquoactionsrsquo=gtarray(rsquoindexrsquo rsquoviewrsquo)

rsquousersrsquo=gtarray(rsquorsquo)

)

array(rsquoallowrsquo allow authenticated users to perform any action

rsquousersrsquo=gtarray(rsquorsquo)

)

array(rsquodenyrsquo deny all users

rsquousersrsquo=gtarray(rsquorsquo)

)

)

The above rules state that all users can access the index and view actions and authenti-cated users can access any actions including the admin action The user should be denied

24 3 Post Management

access in any other scenario Note that these rules are evaluated in the order they arelisted here The first rule matching the current context makes the access decision Forexample if the current user is the system owner who tries to visit the post creation pagethe second rule will match and it will give the access to the user

322 Customizing create and update Operations

The create and update operations are very similar They both need to display an HTMLform to collect user inputs validate them and save them into database The main differ-ence is that the update operation will pre-populate the form with the existing post datafound in the database For this reason the yiic tool generates a partial view wwwroot

blogprotectedviewspost formphp that is embedded in both the create and update

views to render the needed HTML form

We first change the formphp file so that the HTML form only collects the inputs wewant title content tags and status We use plain text fields to collect inputs for thefirst three attributes and a dropdown list to collect input for status The dropdown listoptions are the text displays of the possible post statuses

ltphp echo CHtmlactiveDropDownList($postrsquostatusrsquoLookupitems(rsquoPostStatusrsquo)) gt

In the above we call Lookupitems(rsquoPostStatusrsquo) to bring back the list of post statuses

We then modify the Post class so that it can automatically set some attributes (eg create

time author id) before a post is saved to the database We override the beforeSave()

method as follows

protected function beforeSave()

if(parentbeforeSave())

if($this-gtisNewRecord)

$this-gtcreate time=$this-gtupdate time=time()

$this-gtauthor id=Yiiapp()-gtuser-gtid

else

$this-gtupdate time=time()

return true

else

return false

33 Displaying Posts 25

When we save a post we want to update the tbl tag table to reflect the change of tagfrequencies We can do this work in the afterSave() method which is automaticallyinvoked by Yii after a post is successfully saved into the database

protected function afterSave()

parentafterSave()

Tagmodel()-gtupdateFrequency($this-gt oldTags $this-gttags)

private $ oldTags

protected function afterFind()

parentafterFind()

$this-gt oldTags=$this-gttags

In the implementation because we want to detect if the user changes the tags in case he isupdating an existing post we need to know what the old tags are For this reason we alsowrite the afterFind() method to keep the old tags in the variable oldTags The methodafterFind() is invoked automatically by Yii when an AR record is populated with thedata from database

We are not going to give details of the TagupdateFrequency() method here Interestedreaders may refer to the file wwwrootyiidemosblogprotectedmodelsTagphp

33 Displaying Posts

In our blog application a post may be displayed among a list of posts or by itself Theformer is implemented as the index operation while the latter the view operation In thissection we customize both operations to fulfill our initial requirements

331 Customizing view Operation

The view operation is implemented by the actionView() method in PostController Itsdisplay is generated by the view view with the view file wwwrootblogprotectedviews

postviewphp

Below is the relevant code implementing the view operation in PostController

public function actionView()

26 3 Post Management

$this-gtrender(rsquoviewrsquoarray(

$post=$this-gtloadModel()

$this-gtrender(rsquoviewrsquoarray(

rsquomodelrsquo=gt$post

))

))

private $ model

public function loadModel()

if($this-gt model===null)

if(isset($ GET[rsquoidrsquo]))

if(Yiiapp()-gtuser-gtisGuest)

$condition=rsquostatus=rsquoPostSTATUS PUBLISHED

rsquo OR status=rsquoPOSTSTATUS ARCHIVED

else

$condition=rsquorsquo

$this-gt model=Postmodel()-gtfindbyPk($ GET[rsquoidrsquo] $condition)

if($this-gt model===null)

throw new CHttpException(404rsquoThe requested page does not existrsquo)

return $this-gt model

Our change mainly lies in the loadModel() method In this method we query the Post

table according to the id GET parameter If the post is not found or if it is not publishedor archived (when the user is a guest) we will throw a 404 HTTP error Otherwise thepost object is returned to actionView() which in turn passes the post object to the viewscript for further display

Tip Yii captures HTTP exceptions (instances of CHttpException) and displaysthem in either predefined templates or customized error views The skeleton appli-cation generated by yiic already contains a customized error view in wwwroot

blogprotectedviewssiteerrorphp We can modify this file if we want tofurther customize the error display

The change in the view script is mainly about ajdusting the formatting and styles of thepost display We will not go into details here Interested readers may refer to wwwroot

blogprotectedviewspostviewphp

33 Displaying Posts 27

332 Customizing index Operation

Like the view operation we customize the index operation in two places the actionIndex()

method in PostController and the view file wwwrootblogprotectedviewspostindex

php We mainly need to add the support for displaying a list of posts that are associatedwith a specified tag

Below is the modified actionIndex() method in PostController

public function actionIndex()

$criteria=new CDbCriteria(array(

rsquoconditionrsquo=gtrsquostatus=rsquoPostSTATUS PUBLISHED

rsquoorderrsquo=gtrsquoupdate time DESCrsquo

rsquowithrsquo=gtrsquocommentCountrsquo

))

if(isset($ GET[rsquotagrsquo]))

$criteria-gtaddSearchCondition(rsquotagsrsquo$ GET[rsquotagrsquo])

$dataProvider=new CActiveDataProvider(rsquoPostrsquo array(

rsquopaginationrsquo=gtarray(

rsquopageSizersquo=gtYiiapp()-gtparams[rsquopostsPerPagersquo]

)

rsquocriteriarsquo=gt$criteria

))

$this-gtrender(rsquoindexrsquoarray(

rsquodataProviderrsquo=gt$dataProvider

))

In the above we first create a query criteria for retrieving post list The criteria statesthat only published posts should be returned and they should be sorted according to theirupdate time in descending order Because when displaying a post in the list we want toshow how many comments the post has received in the criteria we also specify to bringback commentCount which if you remember is a relation declared in Postrelations()

In case when a user wants to see posts with a specific tag we would add a search conditionto the criteria to look for the specified tag

Using the query criteria we create a data provider which mainly serves for three purposesFirst it does pagination of the data when too many results may be returned Herewe customize the pagination by setting the page size to be an application parameterpostsPerPage Second it does sorting according to the user request And finally it feedsthe paginated and sorted data to widgets or view code for presentation

28 3 Post Management

After we finish with actionIndex() we modify the index view as follows Our change ismainly about adding the h1 header when the user specifies to display posts with a tag

ltphp if(empty($ GET[rsquotagrsquo])) gt

lth1gtPosts Tagged with ltigtltphp echo CHtmlencode($ GET[rsquotagrsquo]) gtltigtlth1gt

ltphp endif gt

ltphp $this-gtwidget(rsquoziiwidgetsCListViewrsquo array(

rsquodataProviderrsquo=gt$dataProvider

rsquoitemViewrsquo=gtrsquo viewrsquo

rsquotemplatersquo=gtitemsnpager)) gt

Note that in the above we use [CListView] to display the post list This widget requires apartial view to display the detail of each individual post Here we specify the partial viewto be view which means the file wwwrootblogprotectedviewspost viewphp In thisview script we can acccess the post instance being displayed via a local variable named$data

34 Managing Posts

Managing posts mainly refers to listing posts in an administrative view that allows us tosee posts with all statuses updating them and deleting them They are accomplished bythe admin operation and the delete operation respectively The code generated by yiic

does not need much modification Below we mainly explain how these two operations areimplemented

341 Listing Posts in Tabular View

The admin operation shows posts with all statuses in a tabular view The view supportssorting and pagination The following is the actionAdmin() method in PostController

public function actionAdmin()

$dataProvider=new CActiveDataProvider(rsquoPostrsquo array(

rsquosortrsquo=gtarray(

rsquodefaultOrderrsquo=gtrsquostatus update time DESCrsquo

)

))

$this-gtrender(rsquoadminrsquoarray(

rsquodataProviderrsquo=gt$dataProvider

))

34 Managing Posts 29

The above code is very similar to that in actionIndex() The main difference is in thequery criteria Here we do not specify any particular query criteria since we want to seeall posts We do customize the sort property of the data provider so that by default theposts are shown in the order of their statuses and update time

Below is the code for the admin view

lth1gtManage Postslth1gt

ltphp $this-gtwidget(rsquoziiwidgetsgridCGridViewrsquo array(

rsquodataProviderrsquo=gt$dataProvider

rsquocolumnsrsquo=gtarray(

array(

rsquonamersquo=gtrsquotitlersquo

rsquotypersquo=gtrsquorawrsquo

rsquovaluersquo=gtrsquoCHtmllink(CHtmlencode($data-gttitle) $data-gturl)rsquo

)

array(

rsquonamersquo=gtrsquostatusrsquo

rsquovaluersquo=gtrsquoLookupitem(PostStatus$data-gtstatus)rsquo

)

rsquocreate timedatetimersquo

rsquoupdate timedatetimersquo

array(

rsquoclassrsquo=gtrsquoCButtonColumnrsquo

)

)

)) gt

We use [CGridView] to display the posts It allows us to sort by a column and paginatethrough the posts if there are too many to be displayed in a single page Our change ismainly about how to display each column For example for the title column we specifythat it should be displayed as a hyperlink that points to the detailed view of the post

Tip When displaying text we call CHtmlencode() to encode HTML entities init This prevents from cross-site scripting attack

342 Deleting Posts

In the admin data grid there is a delete button in each row Clicking on the button shoulddelete the corresponding post Internally this triggers the delete action implemented asfollows

30 3 Post Management

public function actionDelete()

if(Yiiapp()-gtrequest-gtisPostRequest)

we only allow deletion via POST request

$this-gtloadModel()-gtdelete()

if(isset($ POST[rsquoajaxrsquo]))

$this-gtredirect(array(rsquoindexrsquo))

else

throw new CHttpException(400rsquoInvalid request Please do not repeat this request againrsquo)

The above code is the one generated by the yiic tool without any change We wouldlike to explain a little bit more about the checking on $ POST[rsquoajaxrsquo] The [CGridView]widget has a very nice feature that its sorting pagination and deletion operations are alldone in AJAX mode by default That means the whole page does not get reloaded if anyof the above operations is performed However it is also possible that the widget runs innon-AJAX mode (by setting its ajaxUpdate property to be false or disabling JavaScript onthe client side) It is necessary for the delete action to differentiate these two scenarios ifthe delete request is made via AJAX we should not redirect the user browser otherwisewe should

Deleting a post should also cause the deletion of all comments for that post In additionwe should also update the tbl tag table regarding the tags for the deleted post Both ofthese tasks can be achieved by writing an afterDelete method in the Post model class asfollows

protected function afterDelete()

parentafterDelete()

Commentmodel()-gtdeleteAll(rsquopost id=rsquo$this-gtid)

Tagmodel()-gtupdateFrequency($this-gttags rsquorsquo)

The above code is very straightforward it first deletes all those comments whose post id

is the same as the ID of the deleted post it then updates the tbl tag table for the tags

of the deleted post

34 Managing Posts 31

Tip We have to explicitly delete all comments for the deleted post here becauseSQLite does not really support foreign key constraints In a DBMS that supportsthis constraint (such as MySQL PostgreSQL) the foreign key constraint can be setup such that the DBMS automatically deletes the related comments if the post isdeleted In that case we no longer this explicit deletion call in our code

32 3 Post Management

Chapter 4

Comment Management

41 Customizing Comment Model

For the Comment model we mainly need to customize the rules() and attributeLabels()

methods The attributeLabels() method returns a mapping between attribute namesand attribute labels We do not need to touch relations() since the code generated bythe yiic tool is good enough

411 Customizing rules() Method

We first customize the validation rules generated by the yiic tool The following rules areused for comments

public function rules()

return array(

array(rsquocontent author emailrsquo rsquorequiredrsquo)

array(rsquoauthor email urlrsquo rsquolengthrsquo rsquomaxrsquo=gt128)

array(rsquoemailrsquorsquoemailrsquo)

array(rsquourlrsquorsquourlrsquo)

)

In the above we specify that the author email and content attributes are required thelength of author email and url cannot exceed 128 the email attribute must be a validemail address and the url attribute must be a valid URL

412 Customizing attributeLabels() Method

We then customize the attributeLabels() method to declare the label display for eachmodel attribute This method returns an array consisting of name-label pairs When wecall CHtmlactiveLabel() to display an attribute label

34 4 Comment Management

public function attributeLabels()

return array(

rsquoidrsquo =gt rsquoIdrsquo

rsquocontentrsquo =gt rsquoCommentrsquo

rsquostatusrsquo =gt rsquoStatusrsquo

rsquocreate timersquo =gt rsquoCreate Timersquo

rsquoauthorrsquo =gt rsquoNamersquo

rsquoemailrsquo =gt rsquoEmailrsquo

rsquourlrsquo =gt rsquoWebsitersquo

rsquopost idrsquo =gt rsquoPostrsquo

)

Tip If the label for an attribute is not declared in attributeLabels() an algo-rithm will be used to generate an appropriate label For example a label VerifyCode will be generated for attributes verify code or verifyCode

413 Customizing Saving Process

Because we want to record the creation time of a comment we override the beforeSave()

method of Comment like we do for the Post model

protected function beforeSave()

if(parentbeforeSave())

if($this-gtisNewRecord)

$this-gtcreate time=time()

return true

else

return false

42 Creating and Displaying Comments

In this section we implement the comment display and creation features

421 Displaying Comments

Instead of displaying and creating comments on individual pages we use the post detailpage (generated by the view action of PostController) Below the post content display

42 Creating and Displaying Comments 35

we display first a list of comments belonging to that post and then a comment creationform

In order to display comments on the post detail page we modify the view script wwwrootblogprotectedviewspostviewphp as follows

ltdiv id=commentsgt

ltphp if($model-gtcommentCountgt=1) gt

lth3gt

ltphp echo $model-gtcommentCount rsquocomment(s)rsquo gt

lth3gt

ltphp $this-gtrenderPartial(rsquo commentsrsquoarray(

rsquopostrsquo=gt$model

rsquocommentsrsquo=gt$model-gtcomments

)) gt

ltphp endif gt

ltdivgt

In the above we call renderPartial() to render a partial view named comments to displaythe list of comments belonging to the current post Note that in the view we use theexpression $model-gtcomments to retrieve the comments for the post This is valid becausewe have declared a comments relation in the Post class Evaluating this expression wouldtrigger an implicit JOIN database query to bring back the proper comments This featureis known as lazy relational query

The partial view comments is not very interesting It mainly goes through every commentand displays the detail of it Interested readers may refer to wwwrootyiidemosblog

protectedpost commentsphp

422 Creating Comments

To handle comment creation we first modify the actionView() method of PostController

as follows

public function actionView()

$post=$this-gtloadModel()

$comment=$this-gtnewComment($post)

$this-gtrender(rsquoviewrsquoarray(

rsquomodelrsquo=gt$post

rsquocommentrsquo=gt$comment

))

36 4 Comment Management

protected function newComment($post)

$comment=new Comment

if(isset($ POST[rsquoCommentrsquo]))

$comment-gtattributes=$ POST[rsquoCommentrsquo]

if($post-gtaddComment($comment))

if($comment-gtstatus==CommentSTATUS PENDING)

Yiiapp()-gtuser-gtsetFlash(rsquocommentSubmittedrsquorsquoThank yoursquo)

$this-gtrefresh()

return $comment

In the above we call the newComment() method before we render view In the newComment()

method we generate a Comment instance and check if the comment form is submitted If sowe try to add the comment for the post by calling $post-gtaddComment($comment) If it goesthrough we refresh the post detail page In case the comment needs to be approved we willshow a flash message to indicate this decision A flash message is usually a confirmationmessage displayed to end users If the user clicks on the refresh button of his browser themessage will disappear

We also need to modify wwwrootblogprotectedviewspostviewphp furthermore

ltdiv id=commentsgt

lth3gtLeave a Commentlth3gt

ltphp if(Yiiapp()-gtuser-gthasFlash(rsquocommentSubmittedrsquo)) gt

ltdiv class=successgt

ltphp echo Yiiapp()-gtuser-gtgetFlash(rsquocommentSubmittedrsquo) gt

ltdivgt

ltphp else gt

ltphp $this-gtrenderPartial(rsquocomment formrsquoarray(

rsquomodelrsquo=gt$comment

)) gt

ltphp endif gt

ltdivgtlt-- comments --gt

43 Managing Comments 37

In the above code we display the flash message if it is available If not we displaythe comment input form by rendering the partial view wwwrootblogprotectedviews

comment formphp

43 Managing Comments

Comment management includes updating deleting and approving comments These op-erations are implemented as actions in the CommentController class

431 Updating and Deleting Comments

The code generated by yiic for updating and deleting comments remains largely un-changed

432 Approving Comments

When comments are newly created they are in pending approval status and need to beapproved in order to be visible to guest users Approving a comment is mainly aboutchanging the status column of the comment

We create an actionApprove() method in CommentController as follows

public function actionApprove()

if(Yiiapp()-gtrequest-gtisPostRequest)

$comment=$this-gtloadModel()

$comment-gtapprove()

$this-gtredirect(array(rsquoindexrsquo))

else

throw new CHttpException(400rsquoInvalid requestrsquo)

In the above when the approve action is invoked via a POST request we call the approve()

method defined in the Comment model to change the status We then redirect the userbrowser to the page displaying the post that this comment belongs to

We also modify the actionIndex() method of Comment to show all comments We wouldlike to see comments pending approved to show up first

public function actionIndex()

38 4 Comment Management

$dataProvider=new CActiveDataProvider(rsquoCommentrsquo array(

rsquocriteriarsquo=gtarray(

rsquowithrsquo=gtrsquopostrsquo

rsquoorderrsquo=gtrsquotstatus tcreate time DESCrsquo

)

))

$this-gtrender(rsquoindexrsquoarray(

rsquodataProviderrsquo=gt$dataProvider

))

Notice that in the above code because both tbl post and tbl comment have columns statusand create time we need to disambiguate the corresponding column reference by prefixingthem with table alias names As described in the guide the alias for the primary table ina relational query is always t Therefore we are prefixing t to the status and create time

columns in the above code

Like the post index view the index view for CommentController uses [CListView] to displaythe comment list which in turn uses the partial view wwwrootblogprotectedviews

comment viewphp to display the detail of each individual comment We will not go intodetails here Interested readers may refer to the corresponding file in the blog demowwwrootyiidemosblogprotectedviewscomment viewphp

Chapter 5

Portlets

51 Customizing Page Layout

Features like rdquothe most recent commentsrdquo rdquotag cloudrdquo are better to be implemented inportlets A portlet is a pluggable user interface component that renders a fragment ofHTML code In this section we describe how to set up the portlet architecture for ourblog application

Before we go into details about developing portlets we first need to adjust our page layoutso that the portlets can be placed in a side bar

A page layout determines how to share common sections (eg header footer) amongdifferent pages and how to embed contents that are specific for different pages In Yiia page layout is represented as a view under the protectedviewslayouts folder Whenwe call the render() method in a controller the rendering result will be automaticallyembedded into this layout view before it is displayed to end users The default layoutview is named as main which means the wwwrootblogprotectedviewslayoutsmain

php file in our blog application

We could directly modify the main layout to add a side bar However doing so wouldmake some irrelevant pages such as about contact pages to have a side bar which is notalways desirable For this reason we need different layouts for different pages

We thus create two different layouts column1 and column2 The former displays the pagein a single column while the latter displays two columns in the page one of which is theside bar We will use column2 layout for the post and comment pages and column1 forthe rest of the pages Because both column1 and column2 still share the same header andfooter sections we let them to share the main layout

Based on the above description we create the column1 layout as follows

ltphp $this-gtbeginContent(rsquolayoutsmainrsquo) gt

ltdiv class=containergt

40 5 Portlets

ltdiv id=contentgt

ltphp echo $content gt

ltdivgtlt-- content --gt

ltdivgt

ltphp $this-gtendContent() gt

and the column2 layout as follows

ltphp $this-gtbeginContent(rsquolayoutsmainrsquo) gt

ltdiv class=containergt

ltdiv class=span-18gt

ltdiv id=contentgt

ltphp echo $content gt

ltdivgtlt-- content --gt

ltdivgt

ltdiv class=span-6 lastgt

ltdiv id=sidebargt

portlets to be placed here

ltdivgtlt-- sidebar --gt

ltdivgt

ltdivgt

ltphp $this-gtendContent() gt

As we can see both column1 and column2 are enclosed in a pair of beginContent andendContent calls They essentially use the specified view layoutsmain to decorate theenclosed content In all these layout views the variable $content refers to the dynamiccontent to be embedded into the layout

Besides customizing the layout view file we also need to adjust the CSS file wwwroot

blogcssmaincss so that the overall appearance would look like what we see in the blogdemo We will not go into details here

Info The skeleton application generated by the yiic tool uses the BluePrint CSSframework It provides a nice font reset and a robust grid system For more detailson how to use this framework please refer to its documentation

After creating the new layouts we need to set the layout property of controllers to makeuse of them So far we have three controllers SiteController PostController andCommentController We set SiteController to use column1 as the default layout whilethe rest two of the controllers use column2 for example

class PostController extends Controller

52 Creating User Menu Portlet 41

public $layout=rsquocolumn2rsquo

Tip If some view of the controller does not use the same layout as declaredby the layout property it may customize it in the view by a simple assignment$this-gtlayout=rsquonewlayoutrsquo

52 Creating User Menu Portlet

Based on the requirements analysis we need three different portlets the rdquouser menurdquoportlet the rdquotag cloudrdquo portlet and the rdquorecent commentsrdquo portlet We will develop theseportlets by extending the [CPortlet] widget provided by Yii

In this section we will develop our first concrete portlet - the user menu portlet whichdisplays a list of menu items that are only available to authenticated users The menucontains four items

bull Approve Comments a hyperlink that leads to a list of comments pending approval

bull Create New Post a hyperlink that leads to the post creation page

bull Manage Posts a hyperlink that leads to the post management page

bull Logout a link button that would log out the current user

521 Creating UserMenu Class

We create the UserMenu class to represent the logic part of the user menu portlet Theclass is saved in the file wwwrootblogprotectedcomponentsUserMenuphp which has thefollowing content

Yiiimport(rsquoziiwidgetsCPortletrsquo)

class UserMenu extends CPortlet

public function init()

$this-gttitle=CHtmlencode(Yiiapp()-gtuser-gtname)

parentinit()

42 5 Portlets

protected function renderContent()

$this-gtrender(rsquouserMenursquo)

The UserMenu class extends from the CPortlet class that we created previously It overridesboth the init() method and the renderContent() method of CPortlet The former setsthe portlet title to be the name of the current user the latter generates the portlet bodycontent by rendering a view named userMenu

Tip Notice that we have to explicitly include the CPortlet class by calling Yii

import() before we refer to it the first time This is because CPortlet is part of thezii project ndash the official extension library for Yii For performance considerationclasses in this project are not listed as core classes Therefore we have to import itbefore we use it the first time

522 Creating userMenu View

Next we create the userMenu view which is saved in the file wwwrootblogprotected

componentsviewsuserMenuphp

ltulgt

ltligtltphp echo CHtmllink(rsquoCreate New Postrsquoarray(rsquopostcreatersquo)) gtltligt

ltligtltphp echo CHtmllink(rsquoManage Postsrsquoarray(rsquopostadminrsquo)) gtltligt

ltligtltphp echo CHtmllink(rsquoApprove Commentsrsquoarray(rsquocommentindexrsquo))

rsquo (rsquo Commentmodel()-gtpendingCommentCount rsquo)rsquo gtltligt

ltligtltphp echo CHtmllink(rsquoLogoutrsquoarray(rsquositelogoutrsquo)) gtltligt

ltulgt

Info By default view files for a widget should be placed under the views sub-directory of the directory containing the widget class file The file name must bethe same as the view name

523 Using UserMenu Portlet

It is time for us to make use of our newly completed UserMenu portlet We modify thelayout view file wwwrootblogprotectedviewslayoutscolumn2php as follows

53 Creating Tag Cloud Portlet 43

ltdiv id=sidebargt

ltphp if(Yiiapp()-gtuser-gtisGuest) $this-gtwidget(rsquoUserMenursquo) gt

ltdivgt

In the above we call the widget() method to generate and execute an instance of theUserMenu class Because the portlet should only be displayed to authenticated users weonly call widget() when the isGuest property of the current user is false (meaning theuser is authenticated)

524 Testing UserMenu Portlet

Letrsquos test what we have so far

1 Open a browser window and enter the URL httpwwwexamplecomblogindex

php Verify that there is nothing displayed in the side bar section of the page

2 Click on the Login hyperlink and fill out the login form to login If successful verifythat the UserMenu portlet appears in the side bar and the portlet has the usernameas its title

3 Click on the rsquoLogoutrsquo hyperlink in the UserMenu portlet Verify that the logout actionis successful and the UserMenu portlet disappears

525 Summary

What we have created is a portlet that is highly reusable We can easily reuse it in adifferent project with little or no modification Moreover the design of this portlet followsclosely the philosophy that logic and presentation should be separated While we did notpoint this out in the previous sections such practice is used nearly everywhere in a typicalYii application

53 Creating Tag Cloud Portlet

Tag cloud displays a list of post tags with visual decorations hinting the popularity of eachindividual tag

531 Creating TagCloud Class

We create the TagCloud class in the file wwwrootblogprotectedcomponentsTagCloud

php The file has the following content

44 5 Portlets

Yiiimport(rsquoziiwidgetsCPortletrsquo)

class TagCloud extends CPortlet

public $title=rsquoTagsrsquo

public $maxTags=20

protected function renderContent()

$tags=Tagmodel()-gtfindTagWeights($this-gtmaxTags)

foreach($tags as $tag=gt$weight)

$link=CHtmllink(CHtmlencode($tag) array(rsquopostindexrsquorsquotagrsquo=gt$tag))

echo CHtmltag(rsquospanrsquo array(

rsquoclassrsquo=gtrsquotagrsquo

rsquostylersquo=gtfont-size$weightpt) $link)n

Unlike the UserMenu portlet the TagCloud portlet does not use a view Instead its presen-tation is done in the renderContent() method This is because the presentation does notcontain much HTML tags

We display each tag as a hyperlink to the post index page with the corresponding tagparameter The font size of each tag link is adjusted according to their relative weightamong other tags If a tag has higher frequency value than the other it will have a biggerfont size

532 Using TagCloud Portlet

Usage of the TagCloud portlet is very simple We modify the layout file wwwrootblog

protectedviewslayoutsmainphp as follows

ltdiv id=sidebargt

ltphp if(Yiiapp()-gtuser-gtisGuest) $this-gtwidget(rsquoUserMenursquo) gt

ltphp $this-gtwidget(rsquoTagCloudrsquo array(

rsquomaxTagsrsquo=gtYiiapp()-gtparams[rsquotagCloudCountrsquo]

)) gt

ltdivgt

54 Creating Recent Comments Portlet 45

54 Creating Recent Comments Portlet

In this section we create the last portlet that displays a list of comments recently pub-lished

541 Creating RecentComments Class

We create the RecentComments class in the file wwwrootblogprotectedcomponentsRecentCommentsphp The file has the following content

Yiiimport(rsquoziiwidgetsCPortletrsquo)

class RecentComments extends CPortlet

public $title=rsquoRecent Commentsrsquo

public $maxComments=10

public function getRecentComments()

return Commentmodel()-gtfindRecentComments($this-gtmaxComments)

protected function renderContent()

$this-gtrender(rsquorecentCommentsrsquo)

In the above we invoke the findRecentComments method which is defined in the Comment

class as follows

class Comment extends CActiveRecord

public function findRecentComments($limit=10)

return $this-gtwith(rsquopostrsquo)-gtfindAll(array(

rsquoconditionrsquo=gtrsquotstatus=rsquoselfSTATUS APPROVED

rsquoorderrsquo=gtrsquotcreate time DESCrsquo

rsquolimitrsquo=gt$limit

))

46 5 Portlets

542 Creating recentComments View

The recentComments view is saved in the file wwwrootblogprotectedcomponentsviews

recentCommentsphp It simply displays every comment returned by the RecentComments

getRecentComments() method

543 Using RecentComments Portlet

We modify the layout file wwwrootblogprotectedviewslayoutsmainphp to embedthis last portlet

ltdiv id=sidebargt

ltphp if(Yiiapp()-gtuser-gtisGuest) $this-gtwidget(rsquoUserMenursquo) gt

ltphp $this-gtwidget(rsquoTagCloudrsquo array(

rsquomaxTagsrsquo=gtYiiapp()-gtparams[rsquotagCloudCountrsquo]

)) gt

ltphp $this-gtwidget(rsquoRecentCommentsrsquo array(

rsquomaxCommentsrsquo=gtYiiapp()-gtparams[rsquorecentCommentCountrsquo]

)) gt

ltdivgt

Chapter 6

Final Work

61 Beautifying URLs

The URLs linking various pages of our blog application currently look ugly For examplethe URL for the page showing a post looks like the following

indexphpr=postshowampid=1

In this section we describe how to beautify these URLs and make them SEO-friendlyOur goal is to be able to use the following URLs in the application

1 indexphppostsyii leads to the page showing a list of posts with tag yii

2 indexphppost2A+Test+Post leads to the page showing the detail of the postwith ID 2 whose title is A Test Post

3 indexphppostupdate1 leads to the page that allows updating the post with ID1

Note that in the second URL format we include the post title in the URL This is mainlyto make the URL SEO friendly It is said that search engines may also respect the wordsfound in a URL when it is being indexed

To achieve our goal we modify the application configuration as follows

return array(

rsquocomponentsrsquo=gtarray(

rsquourlManagerrsquo=gtarray(

rsquourlFormatrsquo=gtrsquopathrsquo

rsquorulesrsquo=gtarray(

48 6 Final Work

rsquopostltidd+gtlttitlegtrsquo=gtrsquopostviewrsquorsquopostslttaggtrsquo=gtrsquopostindexrsquo

)

)

)

)

In the above we configure the urlManager component by setting its urlFormat propertyto be path and adding a set of rules

The rules are used by urlManager to parse and create the URLs in the desired formatFor example the first rule says that if a URL indexphppostsyii is requested theurlManager component should be responsible to dispatch the request to the route post

index and generate a tag GET parameter with the value yii On the other hand whencreating a URL with the route postindex and parameter tag the urlManager componentwill also use this rule to generate the desired URL indexphppostsyii For this reasonwe say that urlManager is a two-way URL manager

The urlManager component can further beautify our URLs such as hiding indexphp inthe URLs appending suffix like html to the URLs We can obtain these features easilyby configuring various properties of urlManager in the application configuration For moredetails please refer to the Guide

62 Logging Errors

A production Web application often needs sophisticated logging for various events In ourblog application we would like to log the errors occurring when it is being used Sucherrors could be programming mistakes or usersrsquo misuse of the system Logging these errorswill help us to improve the blog application

We enable the error logging by modifying the application configuration as follows

return array(

rsquopreloadrsquo=gtarray(rsquologrsquo)

rsquocomponentsrsquo=gtarray(

rsquologrsquo=gtarray(

rsquoclassrsquo=gtrsquoCLogRouterrsquo

rsquoroutesrsquo=gtarray(

array(

rsquoclassrsquo=gtrsquoCFileLogRoutersquo

rsquolevelsrsquo=gtrsquoerror warningrsquo

63 Final Tune-up and Deployment 49

)

)

)

)

)

With the above configuration if an error or warning occurs detailed information will belogged and saved in a file located under the directory wwwrootblogprotectedruntime

The log component offers more advanced features such as sending log messages to a listof email addresses displaying log messages in JavaScript console window etc For moredetails please refer to the Guide

63 Final Tune-up and Deployment

We are close to finish our blog application Before deployment we would like to do sometune-ups

631 Changing Home Page

We change to use the post list page as the home page We modify the application config-uration as follows

return array(

rsquodefaultControllerrsquo=gtrsquopostrsquo

)

Tip Because PostController already declares list to be its default action whenwe access the home page of the application we will see the result generated by thelist action of the post controller

632 Enabling Schema Caching

Because ActiveRecord relies on the metadata about tables to determine the column infor-mation it takes time to read the metadata and analyze it This may not be a problemduring development stage but for an application running in production mode it is a totalwaste of time if the database schema does not change Therefore we should enable theschema caching by modifying the application configuration as follows

50 6 Final Work

return array(

rsquocomponentsrsquo=gtarray(

rsquocachersquo=gtarray(

rsquoclassrsquo=gtrsquoCDbCachersquo

)

rsquodbrsquo=gtarray(

rsquoclassrsquo=gtrsquosystemdbCDbConnectionrsquo

rsquoconnectionStringrsquo=gtrsquosqlitewwwrootblogprotecteddatablogdbrsquo

rsquoschemaCachingDurationrsquo=gt3600

)

)

)

In the above we first add a cache component which uses a default SQLite database as thecaching storage If our server is equipped with other caching extensions such as APCwe could change to use them as well We also modify the db component by setting itsschemaCachingDuration property to be 3600 which means the parsed database schemadata can remain valid in cache for 3600 seconds

633 Disabling Debugging Mode

We modify the entry script file wwwrootblogindexphp by removing the line definingthe constant YII DEBUG This constant is useful during development stage because it allowsYii to display more debugging information when an error occurs However when theapplication is running in production mode displaying debugging information is not agood idea because it may contain sensitive information such as where the script file islocated and the content in the file etc

634 Deploying the Application

The final deployment process manly involves copying the directory wwwrootblog to thetarget directory The following checklist shows every needed step

1 Install Yii in the target place if it is not available

2 Copy the entire directory wwwrootblog to the target place

3 Edit the entry script file indexphp by pointing the $yii variable to the new Yiibootstrap file

4 Edit the file protectedyiicphp by setting the $yiic variable to be the new Yiiyiicphp file

64 Future Enhancements 51

5 Change the permission of the directories assets and protectedruntime so that theyare writable by the Web server process

64 Future Enhancements

641 Using a Theme

Without writing any code our blog application is already themeable To use a themewe mainly need to develop the theme by writing customized view files in the theme Forexample to use a theme named classic that uses a different page layout we would createa layout view file wwwrootblogthemesclassicviewslayoutsmainphp We also needto change the application configuration to indicate our choice of the classic theme

return array(

rsquothemersquo=gtrsquoclassicrsquo

)

642 Internationalization

We may also internationalize our blog application so that its pages can be displayed indifferent languages This mainly involves efforts in two aspects

First we may create view files in different languages For example for the list pageof PostController we can create a view file wwwrootblogprotectedviewspostzh cn

listphp When the application is configured to use simplified Chinese (the language codeis zh cn) Yii will automatically use this new view file instead of the original one

Second we may create message translations for those messages generated by code Themessage translations should be saved as files under the directory wwwrootblogprotected

messages We also need to modify the code where we use text strings by enclosing themin the method call Yiit()

For more details about internationalization please refer to the Guide

643 Improving Performance with Cache

While the Yii framework itself is very efficient it is not necessarily true that an applicationwritten in Yii efficient There are several places in our blog application that we can improvethe performance For example the tag clould portlet could be one of the performancebottlenecks because it involves complex database query and PHP logic

52 6 Final Work

We can make use of the sophisticated caching feature provided by Yii to improve theperformance One of the most useful components in Yii is COutputCache which caches afragment of page display so that the underlying code generating the fragment does not needto be executed for every request For example in the layout file wwwrootblogprotected

viewslayoutsmainphp we can enclose the tag cloud portlet with COutputCache

ltphp if($this-gtbeginCache(rsquotagCloudrsquo array(rsquodurationrsquo=gt3600))) gt

ltphp $this-gtwidget(rsquoTagCloudrsquo array(

rsquomaxTagsrsquo=gtYiiapp()-gtparams[rsquotagCloudCountrsquo]

)) gt

ltphp $this-gtendCache() gt

With the above code the tag cloud display will be served from cache instead of beinggenerated on-the-fly for every request The cached content will remain valid in cache for3600 seconds

644 Adding New Features

Our blog application only has very basic functionalities To become a complete blogsystem more features are needed for example calendar portlet email notifications postcategorization archived post portlet and so on We will leave the implementation of thesefeatures to interested readers

  • Contents
  • License
  • Getting Started
    • Building a Blog System using Yii
    • Testdriving with Yii
      • Installing Yii
      • Creating Skeleton Application
      • Application Workflow
        • Requirements Analysis
        • Overall Design
          • Initial Prototyping
            • Setting Up Database
              • Creating Database
              • Establishing Database Connection
                • Scaffolding
                • Authenticating User
                • Summary
                  • Post Management
                    • Customizing Post Model
                      • Customizing rules() Method
                      • Customizing relations() Method
                      • Representing Status in Text
                        • Creating and Updating Posts
                          • Customizing Access Control
                          • Customizing create and update Operations
                            • Displaying Posts
                              • Customizing view Operation
                              • Customizing index Operation
                                • Managing Posts
                                  • Listing Posts in Tabular View
                                  • Deleting Posts
                                      • Comment Management
                                        • Customizing Comment Model
                                          • Customizing rules() Method
                                          • Customizing attributeLabels() Method
                                          • Customizing Saving Process
                                            • Creating and Displaying Comments
                                              • Displaying Comments
                                              • Creating Comments
                                                • Managing Comments
                                                  • Updating and Deleting Comments
                                                  • Approving Comments
                                                      • Portlets
                                                        • Customizing Page Layout
                                                        • Creating User Menu Portlet
                                                          • Creating UserMenu Class
                                                          • Creating userMenu View
                                                          • Using UserMenu Portlet
                                                          • Testing UserMenu Portlet
                                                          • Summary
                                                            • Creating Tag Cloud Portlet
                                                              • Creating TagCloud Class
                                                              • Using TagCloud Portlet
                                                                • Creating Recent Comments Portlet
                                                                  • Creating RecentComments Class
                                                                  • Creating recentComments View
                                                                  • Using RecentComments Portlet
                                                                      • Final Work
                                                                        • Beautifying URLs
                                                                        • Logging Errors
                                                                        • Final Tune-up and Deployment
                                                                          • Changing Home Page
                                                                          • Enabling Schema Caching
                                                                          • Disabling Debugging Mode
                                                                          • Deploying the Application
                                                                            • Future Enhancements
                                                                              • Using a Theme
                                                                              • Internationalization
                                                                              • Improving Performance with Cache
                                                                              • Adding New Features
Page 3: Yii Blog 1.1.0 Demo Tutor

ii Contents

31 Customizing Post Model 19

311 Customizing rules() Method 19

312 Customizing relations() Method 20

313 Representing Status in Text 21

32 Creating and Updating Posts 23

321 Customizing Access Control 23

322 Customizing create and update Operations 24

33 Displaying Posts 25

331 Customizing view Operation 25

332 Customizing index Operation 27

34 Managing Posts 28

341 Listing Posts in Tabular View 28

342 Deleting Posts 29

4 Comment Management 33

41 Customizing Comment Model 33

411 Customizing rules() Method 33

412 Customizing attributeLabels() Method 33

413 Customizing Saving Process 34

42 Creating and Displaying Comments 34

421 Displaying Comments 34

422 Creating Comments 35

43 Managing Comments 37

431 Updating and Deleting Comments 37

432 Approving Comments 37

Contents iii

5 Portlets 39

51 Customizing Page Layout 39

52 Creating User Menu Portlet 41

521 Creating UserMenu Class 41

522 Creating userMenu View 42

523 Using UserMenu Portlet 42

524 Testing UserMenu Portlet 43

525 Summary 43

53 Creating Tag Cloud Portlet 43

531 Creating TagCloud Class 43

532 Using TagCloud Portlet 44

54 Creating Recent Comments Portlet 45

541 Creating RecentComments Class 45

542 Creating recentComments View 46

543 Using RecentComments Portlet 46

6 Final Work 47

61 Beautifying URLs 47

62 Logging Errors 48

63 Final Tune-up and Deployment 49

631 Changing Home Page 49

632 Enabling Schema Caching 49

633 Disabling Debugging Mode 50

634 Deploying the Application 50

64 Future Enhancements 51

iv Contents

641 Using a Theme 51

642 Internationalization 51

643 Improving Performance with Cache 51

644 Adding New Features 52

License of Yii

The Yii framework is free software It is released under the terms of the following BSDLicense

Copyright ccopy2008-2009 by Yii Software LLC All rights reserved

Redistribution and use in source and binary forms with or without modification arepermitted provided that the following conditions are met

1 Redistributions of source code must retain the above copyright notice this list ofconditions and the following disclaimer

2 Redistributions in binary form must reproduce the above copyright notice this list ofconditions and the following disclaimer in the documentation andor other materialsprovided with the distribution

3 Neither the name of Yii Software LLC nor the names of its contributors may beused to endorse or promote products derived from this software without specificprior written permission

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS rdquoAS

ISrdquo AND ANY EXPRESS OR IMPLIED WARRANTIES INCLUDING BUT NOT LIMITED TO THE

IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PUR-

POSE ARE DISCLAIMED IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBU-

TORS BE LIABLE FOR ANY DIRECT INDIRECT INCIDENTAL SPECIAL EXEMPLARY OR

CONSEQUENTIAL DAMAGES (INCLUDING BUT NOT LIMITED TO PROCUREMENT OF SUB-

STITUTE GOODS OR SERVICES LOSS OF USE DATA OR PROFITS OR BUSINESS INTERRUP-

TION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY WHETHER IN CONTRACT

STRICT LIABILITY OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY

WAY OUT OF THE USE OF THIS SOFTWARE EVEN IF ADVISED OF THE POSSIBILITY OF

SUCH DAMAGE

vi Contents

Chapter 1

Getting Started

11 Building a Blog System using Yii

This tutorial describes how to use Yii to develop a blog application shown as the blog demowhich can be found in the Yii release files It explains in detail every step to be takenduring the development which may also be applied in developing other Web applicationsAs a complement to the Guide and the Class Reference of Yii this tutorial aims to showpractical usage of Yii instead of thorough and definitive description

Readers of this tutorial are not required to have prior knowledge about Yii However basicknowledge of object-oriented programming (OOP) and database programming would helpreaders to understand the tutorial more easily

This tutorial is released under the Terms of Yii Documentation

12 Testdriving with Yii

In this section we describe how to create a skeleton application that will serve as ourstarting point For simplicity we assume that the document root of our Web server iswwwroot and the corresponding URL is httpwwwexamplecom

121 Installing Yii

We first install the Yii framework Grab a copy of the Yii release file (version 110 orabove) from wwwyiiframeworkcom and unpack it to the directory wwwrootyii Doublecheck to make sure that there is a directory wwwrootyiiframework

Tip The Yii framework can be installed anywhere in the file system not necessarilyunder a Web folder Its framework directory contains all framework code and isthe only framework directory needed when deploying an Yii application A singleinstallation of Yii can be used by multiple Yii applications

2 1 Getting Started

After installing Yii open a browser window and access the URL httpwwwexample

comyiirequirementsindexphp It shows the requirement checker provided in the Yiirelease For our blog application besides the minimal requirements needed by Yii we alsoneed to enable both the pdo and pdo sqlite PHP extensions so that we can access SQLitedatabases

122 Creating Skeleton Application

We then use the yiic tool to create a skeleton application under the directory wwwroot

blog The yiic tool is a command line tool provided in the Yii release It can be used togenerate code to reduce certain repetitive coding tasks

Open a command window and execute the following command

wwwrootyiiframeworkyiic webapp wwwrootblog

Create a Web application under rsquowwwrootblogrsquo [Yes|No]y

Tip In order to use the yiic tool as shown above the CLI PHP program must beon the command search path If not the following command may be used instead

pathtophp wwwrootyiiframeworkyiicphp webapp wwwrootblog

To try out the application we just created open a Web browser and navigate to the URLhttpwwwexamplecomblogindexphp We should see that our skeleton applicationalready has four fully functional pages the homepage the about page the contact pageand the login page

In the following we briefly describe what we have in this skeleton application

Entry Script

We have an entry script file wwwrootblogindexphp which has the following content

ltphp

$yii=rsquowwwrootframeworkyiiphprsquo

$config=dirname( FILE )rsquoprotectedconfigmainphprsquo

remove the following line when in production mode

defined(rsquoYII DEBUGrsquo) or define(rsquoYII DEBUGrsquotrue)

12 Testdriving with Yii 3

require once($yii)

YiicreateWebApplication($config)-gtrun()

This is the only script that Web users can directly access The script first includes theYii bootstrap file yiiphp It then creates an application instance with the specifiedconfiguration and executes the application

Base Application Directory

We also have an application base directory wwwrootblogprotected The majority of ourcode and data will be placed under this directory and it should be protected from beingaccessed by Web users For Apache httpd Web server we place under this directory ahtaccess file with the following content

deny from all

For other Web servers please refer to the corresponding manual on how to protect adirectory from being accessed by Web users

123 Application Workflow

To help understand how Yii works we describe the main workflow in our skeleton appli-cation when a user is accessing its contact page

1 The user requests the URL httpwwwexamplecomblogindexphpr=sitecontact

2 The entry script is executed by the Web server to process the request

3 An application instance is created and configured with initial property values speci-fied in the application configuration file wwwrootblogprotectedconfigmainphp

4 The application resolves the request into a controller and a controller action For thecontact page request it is resolved as the site controller and the contact action (theactionContact method in wwwrootblogprotectedcontrollersSiteControllerphp)

5 The application creates the site controller in terms of a SiteController instanceand then executes it

6 The SiteController instance executes the contact action by calling its actionContact()method

4 1 Getting Started

7 The actionContact method renders a view named contact to the Web user Inter-nally this is achieved by including the view file wwwrootblogprotectedviews

sitecontactphp and embedding the result into the layout file wwwrootblog

protectedviewslayoutsmainphp

13 Requirements Analysis

The blog system that we are going to develop is a single user system The owner of thesystem will be able to perform the following actions

bull Login and logout

bull Create update and delete posts

bull Publish unpublish and archive posts

bull Approve and delete comments

All other users are guest users who can perform the following actions

bull Read posts

bull Create comments

Additional Requirements for this system include

bull The homepage of the system should display a list of the most recent posts

bull If a page contains more than 10 posts they should be displayed in pages

bull The system should display a post together with its comments

bull The system should be able to list posts with a specified tag

bull The system should show a cloud of tags indicating their use frequencies

bull The system should show a list of most recent comments

bull The system should be themeable

bull The system should use SEO-friendly URLs

14 Overall Design 5

14 Overall Design

Based on the analysis of the requirements we decide to use the following database tablesto store the persistent data for our blog application

bull tbl user stores the user information including username and password

bull tbl post stores the blog post information It mainly consists of the following columns

ndash title required title of the post

ndash content required body content of the post which uses the Markdown format

ndash status required status of the post which can be one of following values

lowast 1 meaning the post is in draft and is not visible to public

lowast 2 meaning the post is published to public

lowast 3 meaning the post is outdated and is not visible in the post list (stillaccessible individually though)

ndash tags optional a list of comma-separated words categorizing the post

bull tbl comment stores the post comment information Each comment is associated witha post and mainly consists of the following columns

ndash name required the author name

ndash email required the author email

ndash website optional the author website URL

ndash content required the comment content in plain text format

ndash status required status of the comment which indicates whether the commentis approved (value 2) or not (value 1)

bull tbl tag stores post tag frequency information that is needed to implement the tagcloud feature The table mainly contains the following columns

ndash name required the unique tag name

ndash frequency required the number of times that the tag appears in posts

bull tbl lookup stores generic lookup information It is essentially a map between integervalues and text strings The former is the data representation in our code while thelatter is the corresponding presentation to end users For example we use integer1 to represent the draft post status and string Draft to display this status to endusers This table mainly contains the following columns

6 1 Getting Started

ndash name the textual representation of the data item that is to be displayed to endusers

ndash code the integer representation of the data item

ndash type the type of the data item

ndash position the relative display order of the data item among other items of thesame type

The following entity-relation (ER) diagram shows the table structure and relationshipsabout the above tables

Figure 11 Entity-Relation Diagram of the Blog Database

Complete SQL statements corresponding to the above ER diagram may be found inthe blog demo In our Yii installation they are in the file wwwrootyiidemosblog

protecteddataschemasqlitesql

Info We name all our table names and column names in lower case This is becausedifferent DBMS often have different case-sensitivity treatment and we want to avoidtroubles like this

We also prefix all our tables with tbl This serves for two purposes First the prefixintroduces a namespace to these tables in case when they need to coexist with othertables in the same database which often happens in a shared hosting environmentwhere a single database is being used by multiple applications Second using tableprefix reduces the possibility of having some table names that are reserved keywordsin DBMS

14 Overall Design 7

We divide the development of our blog application into the following milestones

bull Milestone 1 creating a prototype of the blog system It should consist of most ofthe required functionalities

bull Milestone 2 completing post management It includes creating listing showingupdating and deleting posts

bull Milestone 3 completing comment management It includes creating listing ap-proving updating and deleting post comments

bull Milestone 4 implementing portlets It includes user menu login tag cloud andrecent comments portlets

bull Milestone 5 final tune-up and deployment

8 1 Getting Started

Chapter 2

Initial Prototyping

21 Setting Up Database

Having created a skeleton application and finished the database design in this section wewill create the blog database and establish the connection to it in the skeleton application

211 Creating Database

We choose to create a SQLite database Because the database support in Yii is built on topof PDO we can easily switch to use a different type of DBMS (eg MySQL PostgreSQL)without the need to change our application code

We create the database file blogdb under the directory wwwrootblogprotecteddataNote that both the directory and the database file have to be writable by the Web serverprocess as required by SQLite We may simply copy the database file from the blogdemo in our Yii installation which is located at wwwrootyiidemosblogprotecteddatablogdb We may also generate the database by executing the SQL statements in the filewwwrootyiidemosblogprotecteddataschemasqlitesql

Tip To execute SQL statements we may use the sqlite3 command line tool thatcan be found in the SQLite official website

212 Establishing Database Connection

To use the blog database in the skeleton application we created we need to modify itsapplication configuration which is stored in the PHP script wwwrootblogprotected

configmainphp The script returns an associative array consisting of name-value pairseach of which is used to initialize a writable property of the application instance

We configure the db component as follows

10 2 Initial Prototyping

return array(

rsquocomponentsrsquo=gtarray(

rsquodbrsquo=gtarray(

rsquoconnectionStringrsquo=gtrsquosqlitewwwrootblogprotecteddatablogdbrsquo

rsquotablePrefixrsquo=gtrsquotbl rsquo

)

)

)

The above configuration says that we have a db application component whose connectionStringproperty should be initialized as sqlitewwwrootblogprotecteddatablogdb and whosetablePrefix property should be tbl

With this configuration we can access the DB connection object using Yiiapp()-gtdb

at any place in our code Note that Yiiapp() returns the application instance that wecreate in the entry script If you are interested in possible methods and properties that theDB connection has you may refer to its class reference However in most cases we are notgoing to use this DB connection directly Instead we will use the so-called ActiveRecordto access the database

We would like to explain a bit more about the tablePrefix property that we set in theconfiguration This tells the db connection that it should respect the fact we are usingtbl as the prefix to our database table names In particular if in a SQL statement thereis a token enclosed within double curly brackets (eg post then the db connectionshould translate it into a name with the table prefix (eg tbl post) before sending it toDBMS for execution This feature is especially useful if in future we need to modify thetable name prefix without touching our source code For example if we are developing ageneric content management system (CMS) we may exploit this feature so that when itis being installed in a new environment we can allow users to choose a table prefix theylike

22 Scaffolding 11

Tip If you want to use MySQL instead of SQLite to store data you may cre-ate a MySQL database named blog using the SQL statements in wwwrootyii

demosblogprotecteddataschemamysqlsql Then modify the applicationconfiguration as follows

return array(

rsquocomponentsrsquo=gtarray(

rsquodbrsquo=gtarray(

rsquoconnectionStringrsquo =gt rsquomysqlhost=localhostdbname=blogrsquo

rsquoemulatePreparersquo =gt true

rsquousernamersquo =gt rsquorootrsquo

rsquopasswordrsquo =gt rsquorsquo

rsquocharsetrsquo =gt rsquoutf8rsquo

rsquotablePrefixrsquo =gt rsquotbl rsquo

)

)

)

22 Scaffolding

Create read update and delete (CRUD) are the four basic operations of data objects in anapplication Because the task of implementing the CRUD operations is so common whendeveloping Web applications Yii provides a tool to automate this process (also known asscaffolding) In this section we will describe how to use the tool to implement CRUD forposts and comments

Open a command window and run the following commands

wwwrootyiiframeworkyiic shell wwwrootblogindexphp

Yii Interactive Tool v11

Please type rsquohelprsquo for help Type rsquoexitrsquo to quit

gtgt model

gtgt crud Post

gtgt crud Comment

gtgt exit

12 2 Initial Prototyping

Info Some PHP installations may use a different phpini file for command line(CLI) PHP parser As a result when running the above yiic commands youmay encounter errors like rdquoYiiBaseinclude(PDOphp) failed to open streamrdquoor rdquocould not find driverrdquo Please double check your CLI PHP configuration byexecuting the following command

php -r phpinfo()

The result of the above command will show which phpini file is being used andwhich extensions are loaded If a wrong phpini file is used you may use thefollowing command to explicitly specify the correct phpini to use

php -c phpini wwwrootyiiframeworkyiicphp shell wwwrootblogindexphp

The commands above accomplish two tasks First the model command generates a modelclass file for each table in the blog database Second the crud commands generate thecode needed by the CRUD operations for the Post and Comment models

Tip The command model generates a model class for every table in the databaseSometimes we probably donrsquot want to do so (eg the database contains some irrel-evant tables) In this case we can create model classes one by one For exampleto create the User model we can use the command model User The model com-mand also has some more advanced usages For more details execute help model

command

We can test the generated code by accessing the following URLs

httpwwwexamplecomblogindexphpr=post

httpwwwexamplecomblogindexphpr=comment

Notice that the post and comment features implemented by the generated code are com-pletely independent of each other Also when creating a new post or comment we arerequired to enter information such as author id and create time which in real applica-tion should be set by the program Donrsquot worry We will fix these problems in the nextmilestones For now we should be fairly satisfied as this prototype already contains mostfeatures that we need to implement for the blog application

To prepare for the next milestones letrsquos take a closer look at the files generated by theabove commands All the files are generated under wwwrootblogprotected For conve-nience we group them into model files controller files and view files

22 Scaffolding 13

bull model files

ndash modelsUserphp contains the User class that extends from CActiveRecord andcan be used to access the tbl user database table

ndash modelsPostphp contains the Post class that extends from CActiveRecord andcan be used to access the tbl post database table

ndash modelsTagphp contains the Tag class that extends from CActiveRecord andcan be used to access the tbl tag database table

ndash modelsCommentphp contains the Comment class that extends from CActiveRe-cord and can be used to access the tbl comment database table

ndash modelsLookupphp contains the Lookup class that extends from CActiveRecordand can be used to access the tbl lookup database table

bull controller file

ndash controllersPostControllerphp contains the PostController class which is thecontroller in charge of all CRUD operations about posts

ndash controllersCommentControllerphp contains the CommentController class whichis the controller in charge of all CRUD operations about comments

bull view files

ndash viewspostcreatephp is the view file that shows an HTML form to create anew post

ndash viewspostupdatephp is the view file that shows an HTML form to update anexisting post

ndash viewspostviewphp is the view file that displays the detailed information of apost

ndash viewspostindexphp is the view file that displays a list of posts

ndash viewspostadminphp is the view file that displays posts in a table with ad-ministrative commands

ndash viewspost formphp is the partial view file embedded in viewspostcreate

php and viewspostupdatephp It displays the HTML form for collecting postinformation

ndash viewspost viewphp is the partial view file used by viewspostindexphp Itdisplays the brief view of a single post

ndash a similar set of view files are also generated for comment

14 2 Initial Prototyping

In order to understand better how the above files are used we show in the following theworkflow that occurs in the blog application when displaying a list of posts

1 The user requests the URL httpwwwexamplecomblogindexphpr=posts

2 The entry script is executed by the Web server which creates and initializes anapplication instance to handle the request

3 The application creates an instance of PostController and executes it

4 The PostController instance executes the index action by calling its actionIndex()

method Note that index is the default action if the user does not specify an actionto execute in the URL

5 The actionIndex() method queries database to bring back the list of recent posts

6 The actionIndex() method renders the index view with the post data

23 Authenticating User

Our blog application needs to differentiate between the system owner and guest usersTherefore we need to implement the user authentication feature

As you may have found that the skeleton application already provides user authenticationby checking if the username and password are both demo or admin In this section wewill modify the corresponding code so that the authentication is done against the User

database table

User authentication is performed in a class implementing the IUserIdentity interface Theskeleton application uses the UserIdentity class for this purpose The class is stored inthe file wwwrootblogprotectedcomponentsUserIdentityphp

Tip By convention the name of a class file must be the same as the correspondingclass name suffixed with the extension php Following this convention one canrefer to a class using a path alias For example we can refer to the UserIdentity

class with the alias applicationcomponentsUserIdentity Many APIs in Yiican recognize path aliases (eg YiicreateComponent()) and using path aliasesavoids the necessity of embedding absolute file paths in the code The existence ofthe latter often causes trouble when we deploy an application

We modify the UserIdentity class as follows

23 Authenticating User 15

ltphp

class UserIdentity extends CUserIdentity

private $ id

public function authenticate()

$username=strtolower($this-gtusername)

$user=Usermodel()-gtfind(rsquoLOWER(username)=rsquoarray($username))

if($user===null)

$this-gterrorCode=selfERROR USERNAME INVALID

else if($user-gtvalidatePassword($this-gtpassword))

$this-gterrorCode=selfERROR PASSWORD INVALID

else

$this-gt id=$user-gtid

$this-gtusername=$user-gtusername

$this-gterrorCode=selfERROR NONE

return $this-gterrorCode==selfERROR NONE

public function getId()

return $this-gt id

In the authenticate() method we use the User class to look for a row in the User tablewhose username column is the same as the given username in a case-insensitive mannerRemember that the User class was created using the yiic tool in the prior section Becausethe User class extends from CActiveRecord we can exploit the ActiveRecord feature toaccess the User table in an OOP fashion

In order to check if the user has entered a valid password we invoke the validatePassword

method of the User class We need to modify the file wwwrootblogprotectedmodels

Userphp as follows Note that instead of storing the plain password in the database westore the hash result of the password and a randomly generated salt key When validatingthe user-entered password we should compare the hash results instead

class User extends CActiveRecord

public function validatePassword($password)

return $this-gthashPassword($password$this-gtsalt)===$this-gtpassword

16 2 Initial Prototyping

public function hashPassword($password$salt)

return md5($salt$password)

In the UserIdentity class we also override the getId() method which returns the id valueof the user found in the User table The parent implementation would return the usernameinstead Both the username and id properties will be stored in the user session and maybe accessed via Yiiapp()-gtuser from anywhere in our code

Tip In the UserIdentity class we reference the class CUserIdentity without ex-plicitly including the corresponding class file This is because CUserIdentity is acore class provided by the Yii framework Yii will automatically include the classfile for any core class when it is referenced for the first time

We also do the same with the User class This is because the User class file is placedunder the directory wwwrootblogprotectedmodels which has been added tothe PHP include path according to the following lines found in the applicationconfiguration

return array(

rsquoimportrsquo=gtarray(

rsquoapplicationmodelsrsquo

rsquoapplicationcomponentsrsquo

)

)

The above configuration says that any class whose class file is located under either wwwrootblogprotectedmodels or wwwrootblogprotectedcomponents willbe automatically included when the class is referenced for the first time

The UserIdentity class is mainly used by the LoginForm class to authenticate a user basedon the username and password input collected from the login page The following codefragment shows how UserIdentity is used

$identity=new UserIdentity($username$password)

$identity-gtauthenticate()

switch($identity-gterrorCode)

24 Summary 17

case UserIdentityERROR NONE

Yiiapp()-gtuser-gtlogin($identity)

break

Info People often get confused about identity and the user application componentThe former represents a way of performing authentication while the latter is usedto represent the information related with the current user An application can onlyhave one user component but it can have one or several identity classes dependingon what kind of authentication it supports Once authenticated an identity instancemay pass its state information to the user component so that they are globallyaccessible via user

To test the modified UserIdentity class we can browse the URL httpwwwexample

comblogindexphp and try logging in with the username and password that we store inthe User table If we use the database provided by the blog demo we should be able tologin with username demo and password demo Note that this blog system does not providethe user management feature As a result a user cannot change his account or create anew one through the Web interface The user management feature may be considered asa future enhancement to the blog application

24 Summary

We have completed the milestone 1 Letrsquos summarize what we have done so far

1 We identified the requirements to be fulfilled

2 We installed the Yii framework

3 We created a skeleton application

4 We designed and created the blog database

5 We modified the application configuration by adding the database connection

6 We generated the code that implements the basic CRUD operations for both postsand comments

7 We modified the authentication method to check against the tbl user table

18 2 Initial Prototyping

For a new project most of the time will be spent in step 1 and 4 for this first milestone

Although the code generated by the yiic tool implements fully functional CRUD oper-ations for a database table it often needs to be modified in practical applications Forthis reason in the next two milestone our job is to customize the generated CRUD codeabout posts and comments so that it reaches our initial requirements

In general we first modify the model class file by adding appropriate validation rules anddeclaring relational objects We then modify the controller action and view code for eachindividual CRUD operation

Chapter 3

Post Management

31 Customizing Post Model

The Post model class generated by the yiic tool mainly needs to be modified in two places

bull the rules() method specifies the validation rules for the model attributes

bull the relations() method specifies the related objects

Info A model consists of a list of attributes each associated with a column in thecorresponding database table Attributes can be declared explicitly as class membervariables or implicitly without any declaration

311 Customizing rules() Method

We first specify the validation rules which ensure the attribute values entered by usersare correct before they are saved to the database For example the status attribute ofPost should be an integer 1 2 or 3 The yiic tool also generates validation rules for eachmodel However these rules are based on the table column information and may not beappropriate

Based on the requirement analysis we modify the rules() method as follows

public function rules()

return array(

array(rsquotitle content statusrsquo rsquorequiredrsquo)

array(rsquotitlersquo rsquolengthrsquo rsquomaxrsquo=gt128)

array(rsquostatusrsquo rsquoinrsquo rsquorangersquo=gtarray(123))

array(rsquotagsrsquo rsquomatchrsquo rsquopatternrsquo=gtrsquo^[ws]+$rsquorsquomessagersquo=gtrsquoTags can only contain word charactersrsquo)

20 3 Post Management

array(rsquotagsrsquo rsquonormalizeTagsrsquo)

)

In the above we specify that the title content and status attributes are required thelength of title should not exceed 128 the status attribute value should be 1 (draft) 2(published) or 3 (archived) and the tags attribute should only contain word charactersand commas In addition we use normalizeTags to normalize the user-entered tags so thatthe tags are unique and properly separated with commas

The validators such as required length in and match are all built-in validators providedby Yii The normalizeTags validator is a method-based validator that we need to definein the Post class For more information about how to specify validation rules please referto the Guide

public function normalizeTags($attribute$params)

$this-gttags=Tagarray2string(array unique(Tagstring2array($this-gttags)))

The rules declared in this method are executed one by one when we call the validate() orsave() method of the model instance

Note It is very important to remember that attributes appearing in rules() mustbe those to be entered by end users Other attributes such as id and create time

in the Post model which are set by our code or database should not be in rules()For more details please refer to Securing Attribute Assignments

After making these changes we can visit the post creation page again to verify that thenew validation rules are taking effect

312 Customizing relations() Method

Lastly we customize the relations() method to specify the related objects of a post Bydeclaring these related objects in relations() we can exploit the powerful RelationalActiveRecord (RAR) feature to access the related object information of a post such asits author and comments without the need to write complex SQL JOIN statements

We customize the relations() method as follows

31 Customizing Post Model 21

public function relations()

return array(

rsquoauthorrsquo =gt array(selfBELONGS TO rsquoUserrsquo rsquoauthor idrsquo)

rsquocommentsrsquo =gt array(selfHAS MANY rsquoCommentrsquo rsquopost idrsquo

rsquoconditionrsquo=gtrsquocommentsstatus=rsquoCommentSTATUS APPROVED

rsquoorderrsquo=gtrsquocommentscreate time DESCrsquo)

rsquocommentCountrsquo =gt array(selfSTAT rsquoCommentrsquo rsquopost idrsquo

rsquoconditionrsquo=gtrsquostatus=rsquoCommentSTATUS APPROVED)

)

The above relations state that

bull A post belongs to an author whose class is User and the relationship is establishedbased on the author id attribute value of the post

bull A post has many comments whose class is Comment and the relationship is establishedbased on the post id attribute value of the comments These comments should besorted according to their creation time and the comments must be approved

bull The commentCount relation is a bit special as it returns back an aggregation resultwhich is about how many comments the post has

With the above relation declaration we can easily access the author and comments of apost like the following

$author=$post-gtauthor

echo $author-gtusername

$comments=$post-gtcomments

foreach($comments as $comment)

echo $comment-gtcontent

For more details about how to declare and use relations please refer to the Guide

313 Representing Status in Text

Because the status of a post is stored as an integer in the database we need to provide atextual representation so that it is more intuitive when being displayed to end users In alarge system the similar requirement is very common

22 3 Post Management

As a generic solution we use the tbl lookup table to store the mapping between integervalues and textual representations that are needed by other data objects We modify theLookup model class as follows to more easily access the textual data in the table

class Lookup extends CActiveRecord

private static $ items=array()

public static function items($type)

if(isset(self$ items[$type]))

selfloadItems($type)

return self$ items[$type]

public static function item($type$code)

if(isset(self$ items[$type]))

selfloadItems($type)

return isset(self$ items[$type][$code]) self$ items[$type][$code] false

private static function loadItems($type)

self$ items[$type]=array()

$models=selfmodel()-gtfindAll(array(

rsquoconditionrsquo=gtrsquotype=typersquo

rsquoparamsrsquo=gtarray(rsquotypersquo=gt$type)

rsquoorderrsquo=gtrsquopositionrsquo

))

foreach($models as $model)

self$ items[$type][$model-gtcode]=$model-gtname

Our new code mainly provides two static methods Lookupitems() and Lookupitem()The former returns a list of strings belonging to the specified data type while the latterreturns a particular string for the given data type and data value

Our blog database is pre-populated with two lookup types PostStatus and CommentStatusThe former refers to the possible post statuses while the latter the comment statuses

In order to make our code easier to read we also declare a set of constants to representthe status integer values We should use these constants through our code when referringto the corresponding status values

32 Creating and Updating Posts 23

class Post extends CActiveRecord

const STATUS DRAFT=0

const STATUS PUBLISHED=1

const STATUS ARCHIVED=2

Therefore we can call Lookupitems(rsquoPostStatusrsquo) to get the list of possible post statuses(text strings indexed by the corresponding integer values) and call Lookupitem(rsquoPostStatusrsquoPostSTATUS PUBLISHED) to get the string representation of the published status

32 Creating and Updating Posts

With the Post model ready we need to fine-tune the actions and views for the controllerPostController In this section we first customize the access control of CRUD operationswe then modify the code implementing the create and update operations

321 Customizing Access Control

The first thing we want to do is to customize the access control because the code generatedby yiic does not fit our needs

We modify the accessRules() method in the file wwwrootblogprotectedcontrollers

PostControllerphp as follows

public function accessRules()

return array(

array(rsquoallowrsquo allow all users to perform rsquolistrsquo and rsquoshowrsquo actions

rsquoactionsrsquo=gtarray(rsquoindexrsquo rsquoviewrsquo)

rsquousersrsquo=gtarray(rsquorsquo)

)

array(rsquoallowrsquo allow authenticated users to perform any action

rsquousersrsquo=gtarray(rsquorsquo)

)

array(rsquodenyrsquo deny all users

rsquousersrsquo=gtarray(rsquorsquo)

)

)

The above rules state that all users can access the index and view actions and authenti-cated users can access any actions including the admin action The user should be denied

24 3 Post Management

access in any other scenario Note that these rules are evaluated in the order they arelisted here The first rule matching the current context makes the access decision Forexample if the current user is the system owner who tries to visit the post creation pagethe second rule will match and it will give the access to the user

322 Customizing create and update Operations

The create and update operations are very similar They both need to display an HTMLform to collect user inputs validate them and save them into database The main differ-ence is that the update operation will pre-populate the form with the existing post datafound in the database For this reason the yiic tool generates a partial view wwwroot

blogprotectedviewspost formphp that is embedded in both the create and update

views to render the needed HTML form

We first change the formphp file so that the HTML form only collects the inputs wewant title content tags and status We use plain text fields to collect inputs for thefirst three attributes and a dropdown list to collect input for status The dropdown listoptions are the text displays of the possible post statuses

ltphp echo CHtmlactiveDropDownList($postrsquostatusrsquoLookupitems(rsquoPostStatusrsquo)) gt

In the above we call Lookupitems(rsquoPostStatusrsquo) to bring back the list of post statuses

We then modify the Post class so that it can automatically set some attributes (eg create

time author id) before a post is saved to the database We override the beforeSave()

method as follows

protected function beforeSave()

if(parentbeforeSave())

if($this-gtisNewRecord)

$this-gtcreate time=$this-gtupdate time=time()

$this-gtauthor id=Yiiapp()-gtuser-gtid

else

$this-gtupdate time=time()

return true

else

return false

33 Displaying Posts 25

When we save a post we want to update the tbl tag table to reflect the change of tagfrequencies We can do this work in the afterSave() method which is automaticallyinvoked by Yii after a post is successfully saved into the database

protected function afterSave()

parentafterSave()

Tagmodel()-gtupdateFrequency($this-gt oldTags $this-gttags)

private $ oldTags

protected function afterFind()

parentafterFind()

$this-gt oldTags=$this-gttags

In the implementation because we want to detect if the user changes the tags in case he isupdating an existing post we need to know what the old tags are For this reason we alsowrite the afterFind() method to keep the old tags in the variable oldTags The methodafterFind() is invoked automatically by Yii when an AR record is populated with thedata from database

We are not going to give details of the TagupdateFrequency() method here Interestedreaders may refer to the file wwwrootyiidemosblogprotectedmodelsTagphp

33 Displaying Posts

In our blog application a post may be displayed among a list of posts or by itself Theformer is implemented as the index operation while the latter the view operation In thissection we customize both operations to fulfill our initial requirements

331 Customizing view Operation

The view operation is implemented by the actionView() method in PostController Itsdisplay is generated by the view view with the view file wwwrootblogprotectedviews

postviewphp

Below is the relevant code implementing the view operation in PostController

public function actionView()

26 3 Post Management

$this-gtrender(rsquoviewrsquoarray(

$post=$this-gtloadModel()

$this-gtrender(rsquoviewrsquoarray(

rsquomodelrsquo=gt$post

))

))

private $ model

public function loadModel()

if($this-gt model===null)

if(isset($ GET[rsquoidrsquo]))

if(Yiiapp()-gtuser-gtisGuest)

$condition=rsquostatus=rsquoPostSTATUS PUBLISHED

rsquo OR status=rsquoPOSTSTATUS ARCHIVED

else

$condition=rsquorsquo

$this-gt model=Postmodel()-gtfindbyPk($ GET[rsquoidrsquo] $condition)

if($this-gt model===null)

throw new CHttpException(404rsquoThe requested page does not existrsquo)

return $this-gt model

Our change mainly lies in the loadModel() method In this method we query the Post

table according to the id GET parameter If the post is not found or if it is not publishedor archived (when the user is a guest) we will throw a 404 HTTP error Otherwise thepost object is returned to actionView() which in turn passes the post object to the viewscript for further display

Tip Yii captures HTTP exceptions (instances of CHttpException) and displaysthem in either predefined templates or customized error views The skeleton appli-cation generated by yiic already contains a customized error view in wwwroot

blogprotectedviewssiteerrorphp We can modify this file if we want tofurther customize the error display

The change in the view script is mainly about ajdusting the formatting and styles of thepost display We will not go into details here Interested readers may refer to wwwroot

blogprotectedviewspostviewphp

33 Displaying Posts 27

332 Customizing index Operation

Like the view operation we customize the index operation in two places the actionIndex()

method in PostController and the view file wwwrootblogprotectedviewspostindex

php We mainly need to add the support for displaying a list of posts that are associatedwith a specified tag

Below is the modified actionIndex() method in PostController

public function actionIndex()

$criteria=new CDbCriteria(array(

rsquoconditionrsquo=gtrsquostatus=rsquoPostSTATUS PUBLISHED

rsquoorderrsquo=gtrsquoupdate time DESCrsquo

rsquowithrsquo=gtrsquocommentCountrsquo

))

if(isset($ GET[rsquotagrsquo]))

$criteria-gtaddSearchCondition(rsquotagsrsquo$ GET[rsquotagrsquo])

$dataProvider=new CActiveDataProvider(rsquoPostrsquo array(

rsquopaginationrsquo=gtarray(

rsquopageSizersquo=gtYiiapp()-gtparams[rsquopostsPerPagersquo]

)

rsquocriteriarsquo=gt$criteria

))

$this-gtrender(rsquoindexrsquoarray(

rsquodataProviderrsquo=gt$dataProvider

))

In the above we first create a query criteria for retrieving post list The criteria statesthat only published posts should be returned and they should be sorted according to theirupdate time in descending order Because when displaying a post in the list we want toshow how many comments the post has received in the criteria we also specify to bringback commentCount which if you remember is a relation declared in Postrelations()

In case when a user wants to see posts with a specific tag we would add a search conditionto the criteria to look for the specified tag

Using the query criteria we create a data provider which mainly serves for three purposesFirst it does pagination of the data when too many results may be returned Herewe customize the pagination by setting the page size to be an application parameterpostsPerPage Second it does sorting according to the user request And finally it feedsthe paginated and sorted data to widgets or view code for presentation

28 3 Post Management

After we finish with actionIndex() we modify the index view as follows Our change ismainly about adding the h1 header when the user specifies to display posts with a tag

ltphp if(empty($ GET[rsquotagrsquo])) gt

lth1gtPosts Tagged with ltigtltphp echo CHtmlencode($ GET[rsquotagrsquo]) gtltigtlth1gt

ltphp endif gt

ltphp $this-gtwidget(rsquoziiwidgetsCListViewrsquo array(

rsquodataProviderrsquo=gt$dataProvider

rsquoitemViewrsquo=gtrsquo viewrsquo

rsquotemplatersquo=gtitemsnpager)) gt

Note that in the above we use [CListView] to display the post list This widget requires apartial view to display the detail of each individual post Here we specify the partial viewto be view which means the file wwwrootblogprotectedviewspost viewphp In thisview script we can acccess the post instance being displayed via a local variable named$data

34 Managing Posts

Managing posts mainly refers to listing posts in an administrative view that allows us tosee posts with all statuses updating them and deleting them They are accomplished bythe admin operation and the delete operation respectively The code generated by yiic

does not need much modification Below we mainly explain how these two operations areimplemented

341 Listing Posts in Tabular View

The admin operation shows posts with all statuses in a tabular view The view supportssorting and pagination The following is the actionAdmin() method in PostController

public function actionAdmin()

$dataProvider=new CActiveDataProvider(rsquoPostrsquo array(

rsquosortrsquo=gtarray(

rsquodefaultOrderrsquo=gtrsquostatus update time DESCrsquo

)

))

$this-gtrender(rsquoadminrsquoarray(

rsquodataProviderrsquo=gt$dataProvider

))

34 Managing Posts 29

The above code is very similar to that in actionIndex() The main difference is in thequery criteria Here we do not specify any particular query criteria since we want to seeall posts We do customize the sort property of the data provider so that by default theposts are shown in the order of their statuses and update time

Below is the code for the admin view

lth1gtManage Postslth1gt

ltphp $this-gtwidget(rsquoziiwidgetsgridCGridViewrsquo array(

rsquodataProviderrsquo=gt$dataProvider

rsquocolumnsrsquo=gtarray(

array(

rsquonamersquo=gtrsquotitlersquo

rsquotypersquo=gtrsquorawrsquo

rsquovaluersquo=gtrsquoCHtmllink(CHtmlencode($data-gttitle) $data-gturl)rsquo

)

array(

rsquonamersquo=gtrsquostatusrsquo

rsquovaluersquo=gtrsquoLookupitem(PostStatus$data-gtstatus)rsquo

)

rsquocreate timedatetimersquo

rsquoupdate timedatetimersquo

array(

rsquoclassrsquo=gtrsquoCButtonColumnrsquo

)

)

)) gt

We use [CGridView] to display the posts It allows us to sort by a column and paginatethrough the posts if there are too many to be displayed in a single page Our change ismainly about how to display each column For example for the title column we specifythat it should be displayed as a hyperlink that points to the detailed view of the post

Tip When displaying text we call CHtmlencode() to encode HTML entities init This prevents from cross-site scripting attack

342 Deleting Posts

In the admin data grid there is a delete button in each row Clicking on the button shoulddelete the corresponding post Internally this triggers the delete action implemented asfollows

30 3 Post Management

public function actionDelete()

if(Yiiapp()-gtrequest-gtisPostRequest)

we only allow deletion via POST request

$this-gtloadModel()-gtdelete()

if(isset($ POST[rsquoajaxrsquo]))

$this-gtredirect(array(rsquoindexrsquo))

else

throw new CHttpException(400rsquoInvalid request Please do not repeat this request againrsquo)

The above code is the one generated by the yiic tool without any change We wouldlike to explain a little bit more about the checking on $ POST[rsquoajaxrsquo] The [CGridView]widget has a very nice feature that its sorting pagination and deletion operations are alldone in AJAX mode by default That means the whole page does not get reloaded if anyof the above operations is performed However it is also possible that the widget runs innon-AJAX mode (by setting its ajaxUpdate property to be false or disabling JavaScript onthe client side) It is necessary for the delete action to differentiate these two scenarios ifthe delete request is made via AJAX we should not redirect the user browser otherwisewe should

Deleting a post should also cause the deletion of all comments for that post In additionwe should also update the tbl tag table regarding the tags for the deleted post Both ofthese tasks can be achieved by writing an afterDelete method in the Post model class asfollows

protected function afterDelete()

parentafterDelete()

Commentmodel()-gtdeleteAll(rsquopost id=rsquo$this-gtid)

Tagmodel()-gtupdateFrequency($this-gttags rsquorsquo)

The above code is very straightforward it first deletes all those comments whose post id

is the same as the ID of the deleted post it then updates the tbl tag table for the tags

of the deleted post

34 Managing Posts 31

Tip We have to explicitly delete all comments for the deleted post here becauseSQLite does not really support foreign key constraints In a DBMS that supportsthis constraint (such as MySQL PostgreSQL) the foreign key constraint can be setup such that the DBMS automatically deletes the related comments if the post isdeleted In that case we no longer this explicit deletion call in our code

32 3 Post Management

Chapter 4

Comment Management

41 Customizing Comment Model

For the Comment model we mainly need to customize the rules() and attributeLabels()

methods The attributeLabels() method returns a mapping between attribute namesand attribute labels We do not need to touch relations() since the code generated bythe yiic tool is good enough

411 Customizing rules() Method

We first customize the validation rules generated by the yiic tool The following rules areused for comments

public function rules()

return array(

array(rsquocontent author emailrsquo rsquorequiredrsquo)

array(rsquoauthor email urlrsquo rsquolengthrsquo rsquomaxrsquo=gt128)

array(rsquoemailrsquorsquoemailrsquo)

array(rsquourlrsquorsquourlrsquo)

)

In the above we specify that the author email and content attributes are required thelength of author email and url cannot exceed 128 the email attribute must be a validemail address and the url attribute must be a valid URL

412 Customizing attributeLabels() Method

We then customize the attributeLabels() method to declare the label display for eachmodel attribute This method returns an array consisting of name-label pairs When wecall CHtmlactiveLabel() to display an attribute label

34 4 Comment Management

public function attributeLabels()

return array(

rsquoidrsquo =gt rsquoIdrsquo

rsquocontentrsquo =gt rsquoCommentrsquo

rsquostatusrsquo =gt rsquoStatusrsquo

rsquocreate timersquo =gt rsquoCreate Timersquo

rsquoauthorrsquo =gt rsquoNamersquo

rsquoemailrsquo =gt rsquoEmailrsquo

rsquourlrsquo =gt rsquoWebsitersquo

rsquopost idrsquo =gt rsquoPostrsquo

)

Tip If the label for an attribute is not declared in attributeLabels() an algo-rithm will be used to generate an appropriate label For example a label VerifyCode will be generated for attributes verify code or verifyCode

413 Customizing Saving Process

Because we want to record the creation time of a comment we override the beforeSave()

method of Comment like we do for the Post model

protected function beforeSave()

if(parentbeforeSave())

if($this-gtisNewRecord)

$this-gtcreate time=time()

return true

else

return false

42 Creating and Displaying Comments

In this section we implement the comment display and creation features

421 Displaying Comments

Instead of displaying and creating comments on individual pages we use the post detailpage (generated by the view action of PostController) Below the post content display

42 Creating and Displaying Comments 35

we display first a list of comments belonging to that post and then a comment creationform

In order to display comments on the post detail page we modify the view script wwwrootblogprotectedviewspostviewphp as follows

ltdiv id=commentsgt

ltphp if($model-gtcommentCountgt=1) gt

lth3gt

ltphp echo $model-gtcommentCount rsquocomment(s)rsquo gt

lth3gt

ltphp $this-gtrenderPartial(rsquo commentsrsquoarray(

rsquopostrsquo=gt$model

rsquocommentsrsquo=gt$model-gtcomments

)) gt

ltphp endif gt

ltdivgt

In the above we call renderPartial() to render a partial view named comments to displaythe list of comments belonging to the current post Note that in the view we use theexpression $model-gtcomments to retrieve the comments for the post This is valid becausewe have declared a comments relation in the Post class Evaluating this expression wouldtrigger an implicit JOIN database query to bring back the proper comments This featureis known as lazy relational query

The partial view comments is not very interesting It mainly goes through every commentand displays the detail of it Interested readers may refer to wwwrootyiidemosblog

protectedpost commentsphp

422 Creating Comments

To handle comment creation we first modify the actionView() method of PostController

as follows

public function actionView()

$post=$this-gtloadModel()

$comment=$this-gtnewComment($post)

$this-gtrender(rsquoviewrsquoarray(

rsquomodelrsquo=gt$post

rsquocommentrsquo=gt$comment

))

36 4 Comment Management

protected function newComment($post)

$comment=new Comment

if(isset($ POST[rsquoCommentrsquo]))

$comment-gtattributes=$ POST[rsquoCommentrsquo]

if($post-gtaddComment($comment))

if($comment-gtstatus==CommentSTATUS PENDING)

Yiiapp()-gtuser-gtsetFlash(rsquocommentSubmittedrsquorsquoThank yoursquo)

$this-gtrefresh()

return $comment

In the above we call the newComment() method before we render view In the newComment()

method we generate a Comment instance and check if the comment form is submitted If sowe try to add the comment for the post by calling $post-gtaddComment($comment) If it goesthrough we refresh the post detail page In case the comment needs to be approved we willshow a flash message to indicate this decision A flash message is usually a confirmationmessage displayed to end users If the user clicks on the refresh button of his browser themessage will disappear

We also need to modify wwwrootblogprotectedviewspostviewphp furthermore

ltdiv id=commentsgt

lth3gtLeave a Commentlth3gt

ltphp if(Yiiapp()-gtuser-gthasFlash(rsquocommentSubmittedrsquo)) gt

ltdiv class=successgt

ltphp echo Yiiapp()-gtuser-gtgetFlash(rsquocommentSubmittedrsquo) gt

ltdivgt

ltphp else gt

ltphp $this-gtrenderPartial(rsquocomment formrsquoarray(

rsquomodelrsquo=gt$comment

)) gt

ltphp endif gt

ltdivgtlt-- comments --gt

43 Managing Comments 37

In the above code we display the flash message if it is available If not we displaythe comment input form by rendering the partial view wwwrootblogprotectedviews

comment formphp

43 Managing Comments

Comment management includes updating deleting and approving comments These op-erations are implemented as actions in the CommentController class

431 Updating and Deleting Comments

The code generated by yiic for updating and deleting comments remains largely un-changed

432 Approving Comments

When comments are newly created they are in pending approval status and need to beapproved in order to be visible to guest users Approving a comment is mainly aboutchanging the status column of the comment

We create an actionApprove() method in CommentController as follows

public function actionApprove()

if(Yiiapp()-gtrequest-gtisPostRequest)

$comment=$this-gtloadModel()

$comment-gtapprove()

$this-gtredirect(array(rsquoindexrsquo))

else

throw new CHttpException(400rsquoInvalid requestrsquo)

In the above when the approve action is invoked via a POST request we call the approve()

method defined in the Comment model to change the status We then redirect the userbrowser to the page displaying the post that this comment belongs to

We also modify the actionIndex() method of Comment to show all comments We wouldlike to see comments pending approved to show up first

public function actionIndex()

38 4 Comment Management

$dataProvider=new CActiveDataProvider(rsquoCommentrsquo array(

rsquocriteriarsquo=gtarray(

rsquowithrsquo=gtrsquopostrsquo

rsquoorderrsquo=gtrsquotstatus tcreate time DESCrsquo

)

))

$this-gtrender(rsquoindexrsquoarray(

rsquodataProviderrsquo=gt$dataProvider

))

Notice that in the above code because both tbl post and tbl comment have columns statusand create time we need to disambiguate the corresponding column reference by prefixingthem with table alias names As described in the guide the alias for the primary table ina relational query is always t Therefore we are prefixing t to the status and create time

columns in the above code

Like the post index view the index view for CommentController uses [CListView] to displaythe comment list which in turn uses the partial view wwwrootblogprotectedviews

comment viewphp to display the detail of each individual comment We will not go intodetails here Interested readers may refer to the corresponding file in the blog demowwwrootyiidemosblogprotectedviewscomment viewphp

Chapter 5

Portlets

51 Customizing Page Layout

Features like rdquothe most recent commentsrdquo rdquotag cloudrdquo are better to be implemented inportlets A portlet is a pluggable user interface component that renders a fragment ofHTML code In this section we describe how to set up the portlet architecture for ourblog application

Before we go into details about developing portlets we first need to adjust our page layoutso that the portlets can be placed in a side bar

A page layout determines how to share common sections (eg header footer) amongdifferent pages and how to embed contents that are specific for different pages In Yiia page layout is represented as a view under the protectedviewslayouts folder Whenwe call the render() method in a controller the rendering result will be automaticallyembedded into this layout view before it is displayed to end users The default layoutview is named as main which means the wwwrootblogprotectedviewslayoutsmain

php file in our blog application

We could directly modify the main layout to add a side bar However doing so wouldmake some irrelevant pages such as about contact pages to have a side bar which is notalways desirable For this reason we need different layouts for different pages

We thus create two different layouts column1 and column2 The former displays the pagein a single column while the latter displays two columns in the page one of which is theside bar We will use column2 layout for the post and comment pages and column1 forthe rest of the pages Because both column1 and column2 still share the same header andfooter sections we let them to share the main layout

Based on the above description we create the column1 layout as follows

ltphp $this-gtbeginContent(rsquolayoutsmainrsquo) gt

ltdiv class=containergt

40 5 Portlets

ltdiv id=contentgt

ltphp echo $content gt

ltdivgtlt-- content --gt

ltdivgt

ltphp $this-gtendContent() gt

and the column2 layout as follows

ltphp $this-gtbeginContent(rsquolayoutsmainrsquo) gt

ltdiv class=containergt

ltdiv class=span-18gt

ltdiv id=contentgt

ltphp echo $content gt

ltdivgtlt-- content --gt

ltdivgt

ltdiv class=span-6 lastgt

ltdiv id=sidebargt

portlets to be placed here

ltdivgtlt-- sidebar --gt

ltdivgt

ltdivgt

ltphp $this-gtendContent() gt

As we can see both column1 and column2 are enclosed in a pair of beginContent andendContent calls They essentially use the specified view layoutsmain to decorate theenclosed content In all these layout views the variable $content refers to the dynamiccontent to be embedded into the layout

Besides customizing the layout view file we also need to adjust the CSS file wwwroot

blogcssmaincss so that the overall appearance would look like what we see in the blogdemo We will not go into details here

Info The skeleton application generated by the yiic tool uses the BluePrint CSSframework It provides a nice font reset and a robust grid system For more detailson how to use this framework please refer to its documentation

After creating the new layouts we need to set the layout property of controllers to makeuse of them So far we have three controllers SiteController PostController andCommentController We set SiteController to use column1 as the default layout whilethe rest two of the controllers use column2 for example

class PostController extends Controller

52 Creating User Menu Portlet 41

public $layout=rsquocolumn2rsquo

Tip If some view of the controller does not use the same layout as declaredby the layout property it may customize it in the view by a simple assignment$this-gtlayout=rsquonewlayoutrsquo

52 Creating User Menu Portlet

Based on the requirements analysis we need three different portlets the rdquouser menurdquoportlet the rdquotag cloudrdquo portlet and the rdquorecent commentsrdquo portlet We will develop theseportlets by extending the [CPortlet] widget provided by Yii

In this section we will develop our first concrete portlet - the user menu portlet whichdisplays a list of menu items that are only available to authenticated users The menucontains four items

bull Approve Comments a hyperlink that leads to a list of comments pending approval

bull Create New Post a hyperlink that leads to the post creation page

bull Manage Posts a hyperlink that leads to the post management page

bull Logout a link button that would log out the current user

521 Creating UserMenu Class

We create the UserMenu class to represent the logic part of the user menu portlet Theclass is saved in the file wwwrootblogprotectedcomponentsUserMenuphp which has thefollowing content

Yiiimport(rsquoziiwidgetsCPortletrsquo)

class UserMenu extends CPortlet

public function init()

$this-gttitle=CHtmlencode(Yiiapp()-gtuser-gtname)

parentinit()

42 5 Portlets

protected function renderContent()

$this-gtrender(rsquouserMenursquo)

The UserMenu class extends from the CPortlet class that we created previously It overridesboth the init() method and the renderContent() method of CPortlet The former setsthe portlet title to be the name of the current user the latter generates the portlet bodycontent by rendering a view named userMenu

Tip Notice that we have to explicitly include the CPortlet class by calling Yii

import() before we refer to it the first time This is because CPortlet is part of thezii project ndash the official extension library for Yii For performance considerationclasses in this project are not listed as core classes Therefore we have to import itbefore we use it the first time

522 Creating userMenu View

Next we create the userMenu view which is saved in the file wwwrootblogprotected

componentsviewsuserMenuphp

ltulgt

ltligtltphp echo CHtmllink(rsquoCreate New Postrsquoarray(rsquopostcreatersquo)) gtltligt

ltligtltphp echo CHtmllink(rsquoManage Postsrsquoarray(rsquopostadminrsquo)) gtltligt

ltligtltphp echo CHtmllink(rsquoApprove Commentsrsquoarray(rsquocommentindexrsquo))

rsquo (rsquo Commentmodel()-gtpendingCommentCount rsquo)rsquo gtltligt

ltligtltphp echo CHtmllink(rsquoLogoutrsquoarray(rsquositelogoutrsquo)) gtltligt

ltulgt

Info By default view files for a widget should be placed under the views sub-directory of the directory containing the widget class file The file name must bethe same as the view name

523 Using UserMenu Portlet

It is time for us to make use of our newly completed UserMenu portlet We modify thelayout view file wwwrootblogprotectedviewslayoutscolumn2php as follows

53 Creating Tag Cloud Portlet 43

ltdiv id=sidebargt

ltphp if(Yiiapp()-gtuser-gtisGuest) $this-gtwidget(rsquoUserMenursquo) gt

ltdivgt

In the above we call the widget() method to generate and execute an instance of theUserMenu class Because the portlet should only be displayed to authenticated users weonly call widget() when the isGuest property of the current user is false (meaning theuser is authenticated)

524 Testing UserMenu Portlet

Letrsquos test what we have so far

1 Open a browser window and enter the URL httpwwwexamplecomblogindex

php Verify that there is nothing displayed in the side bar section of the page

2 Click on the Login hyperlink and fill out the login form to login If successful verifythat the UserMenu portlet appears in the side bar and the portlet has the usernameas its title

3 Click on the rsquoLogoutrsquo hyperlink in the UserMenu portlet Verify that the logout actionis successful and the UserMenu portlet disappears

525 Summary

What we have created is a portlet that is highly reusable We can easily reuse it in adifferent project with little or no modification Moreover the design of this portlet followsclosely the philosophy that logic and presentation should be separated While we did notpoint this out in the previous sections such practice is used nearly everywhere in a typicalYii application

53 Creating Tag Cloud Portlet

Tag cloud displays a list of post tags with visual decorations hinting the popularity of eachindividual tag

531 Creating TagCloud Class

We create the TagCloud class in the file wwwrootblogprotectedcomponentsTagCloud

php The file has the following content

44 5 Portlets

Yiiimport(rsquoziiwidgetsCPortletrsquo)

class TagCloud extends CPortlet

public $title=rsquoTagsrsquo

public $maxTags=20

protected function renderContent()

$tags=Tagmodel()-gtfindTagWeights($this-gtmaxTags)

foreach($tags as $tag=gt$weight)

$link=CHtmllink(CHtmlencode($tag) array(rsquopostindexrsquorsquotagrsquo=gt$tag))

echo CHtmltag(rsquospanrsquo array(

rsquoclassrsquo=gtrsquotagrsquo

rsquostylersquo=gtfont-size$weightpt) $link)n

Unlike the UserMenu portlet the TagCloud portlet does not use a view Instead its presen-tation is done in the renderContent() method This is because the presentation does notcontain much HTML tags

We display each tag as a hyperlink to the post index page with the corresponding tagparameter The font size of each tag link is adjusted according to their relative weightamong other tags If a tag has higher frequency value than the other it will have a biggerfont size

532 Using TagCloud Portlet

Usage of the TagCloud portlet is very simple We modify the layout file wwwrootblog

protectedviewslayoutsmainphp as follows

ltdiv id=sidebargt

ltphp if(Yiiapp()-gtuser-gtisGuest) $this-gtwidget(rsquoUserMenursquo) gt

ltphp $this-gtwidget(rsquoTagCloudrsquo array(

rsquomaxTagsrsquo=gtYiiapp()-gtparams[rsquotagCloudCountrsquo]

)) gt

ltdivgt

54 Creating Recent Comments Portlet 45

54 Creating Recent Comments Portlet

In this section we create the last portlet that displays a list of comments recently pub-lished

541 Creating RecentComments Class

We create the RecentComments class in the file wwwrootblogprotectedcomponentsRecentCommentsphp The file has the following content

Yiiimport(rsquoziiwidgetsCPortletrsquo)

class RecentComments extends CPortlet

public $title=rsquoRecent Commentsrsquo

public $maxComments=10

public function getRecentComments()

return Commentmodel()-gtfindRecentComments($this-gtmaxComments)

protected function renderContent()

$this-gtrender(rsquorecentCommentsrsquo)

In the above we invoke the findRecentComments method which is defined in the Comment

class as follows

class Comment extends CActiveRecord

public function findRecentComments($limit=10)

return $this-gtwith(rsquopostrsquo)-gtfindAll(array(

rsquoconditionrsquo=gtrsquotstatus=rsquoselfSTATUS APPROVED

rsquoorderrsquo=gtrsquotcreate time DESCrsquo

rsquolimitrsquo=gt$limit

))

46 5 Portlets

542 Creating recentComments View

The recentComments view is saved in the file wwwrootblogprotectedcomponentsviews

recentCommentsphp It simply displays every comment returned by the RecentComments

getRecentComments() method

543 Using RecentComments Portlet

We modify the layout file wwwrootblogprotectedviewslayoutsmainphp to embedthis last portlet

ltdiv id=sidebargt

ltphp if(Yiiapp()-gtuser-gtisGuest) $this-gtwidget(rsquoUserMenursquo) gt

ltphp $this-gtwidget(rsquoTagCloudrsquo array(

rsquomaxTagsrsquo=gtYiiapp()-gtparams[rsquotagCloudCountrsquo]

)) gt

ltphp $this-gtwidget(rsquoRecentCommentsrsquo array(

rsquomaxCommentsrsquo=gtYiiapp()-gtparams[rsquorecentCommentCountrsquo]

)) gt

ltdivgt

Chapter 6

Final Work

61 Beautifying URLs

The URLs linking various pages of our blog application currently look ugly For examplethe URL for the page showing a post looks like the following

indexphpr=postshowampid=1

In this section we describe how to beautify these URLs and make them SEO-friendlyOur goal is to be able to use the following URLs in the application

1 indexphppostsyii leads to the page showing a list of posts with tag yii

2 indexphppost2A+Test+Post leads to the page showing the detail of the postwith ID 2 whose title is A Test Post

3 indexphppostupdate1 leads to the page that allows updating the post with ID1

Note that in the second URL format we include the post title in the URL This is mainlyto make the URL SEO friendly It is said that search engines may also respect the wordsfound in a URL when it is being indexed

To achieve our goal we modify the application configuration as follows

return array(

rsquocomponentsrsquo=gtarray(

rsquourlManagerrsquo=gtarray(

rsquourlFormatrsquo=gtrsquopathrsquo

rsquorulesrsquo=gtarray(

48 6 Final Work

rsquopostltidd+gtlttitlegtrsquo=gtrsquopostviewrsquorsquopostslttaggtrsquo=gtrsquopostindexrsquo

)

)

)

)

In the above we configure the urlManager component by setting its urlFormat propertyto be path and adding a set of rules

The rules are used by urlManager to parse and create the URLs in the desired formatFor example the first rule says that if a URL indexphppostsyii is requested theurlManager component should be responsible to dispatch the request to the route post

index and generate a tag GET parameter with the value yii On the other hand whencreating a URL with the route postindex and parameter tag the urlManager componentwill also use this rule to generate the desired URL indexphppostsyii For this reasonwe say that urlManager is a two-way URL manager

The urlManager component can further beautify our URLs such as hiding indexphp inthe URLs appending suffix like html to the URLs We can obtain these features easilyby configuring various properties of urlManager in the application configuration For moredetails please refer to the Guide

62 Logging Errors

A production Web application often needs sophisticated logging for various events In ourblog application we would like to log the errors occurring when it is being used Sucherrors could be programming mistakes or usersrsquo misuse of the system Logging these errorswill help us to improve the blog application

We enable the error logging by modifying the application configuration as follows

return array(

rsquopreloadrsquo=gtarray(rsquologrsquo)

rsquocomponentsrsquo=gtarray(

rsquologrsquo=gtarray(

rsquoclassrsquo=gtrsquoCLogRouterrsquo

rsquoroutesrsquo=gtarray(

array(

rsquoclassrsquo=gtrsquoCFileLogRoutersquo

rsquolevelsrsquo=gtrsquoerror warningrsquo

63 Final Tune-up and Deployment 49

)

)

)

)

)

With the above configuration if an error or warning occurs detailed information will belogged and saved in a file located under the directory wwwrootblogprotectedruntime

The log component offers more advanced features such as sending log messages to a listof email addresses displaying log messages in JavaScript console window etc For moredetails please refer to the Guide

63 Final Tune-up and Deployment

We are close to finish our blog application Before deployment we would like to do sometune-ups

631 Changing Home Page

We change to use the post list page as the home page We modify the application config-uration as follows

return array(

rsquodefaultControllerrsquo=gtrsquopostrsquo

)

Tip Because PostController already declares list to be its default action whenwe access the home page of the application we will see the result generated by thelist action of the post controller

632 Enabling Schema Caching

Because ActiveRecord relies on the metadata about tables to determine the column infor-mation it takes time to read the metadata and analyze it This may not be a problemduring development stage but for an application running in production mode it is a totalwaste of time if the database schema does not change Therefore we should enable theschema caching by modifying the application configuration as follows

50 6 Final Work

return array(

rsquocomponentsrsquo=gtarray(

rsquocachersquo=gtarray(

rsquoclassrsquo=gtrsquoCDbCachersquo

)

rsquodbrsquo=gtarray(

rsquoclassrsquo=gtrsquosystemdbCDbConnectionrsquo

rsquoconnectionStringrsquo=gtrsquosqlitewwwrootblogprotecteddatablogdbrsquo

rsquoschemaCachingDurationrsquo=gt3600

)

)

)

In the above we first add a cache component which uses a default SQLite database as thecaching storage If our server is equipped with other caching extensions such as APCwe could change to use them as well We also modify the db component by setting itsschemaCachingDuration property to be 3600 which means the parsed database schemadata can remain valid in cache for 3600 seconds

633 Disabling Debugging Mode

We modify the entry script file wwwrootblogindexphp by removing the line definingthe constant YII DEBUG This constant is useful during development stage because it allowsYii to display more debugging information when an error occurs However when theapplication is running in production mode displaying debugging information is not agood idea because it may contain sensitive information such as where the script file islocated and the content in the file etc

634 Deploying the Application

The final deployment process manly involves copying the directory wwwrootblog to thetarget directory The following checklist shows every needed step

1 Install Yii in the target place if it is not available

2 Copy the entire directory wwwrootblog to the target place

3 Edit the entry script file indexphp by pointing the $yii variable to the new Yiibootstrap file

4 Edit the file protectedyiicphp by setting the $yiic variable to be the new Yiiyiicphp file

64 Future Enhancements 51

5 Change the permission of the directories assets and protectedruntime so that theyare writable by the Web server process

64 Future Enhancements

641 Using a Theme

Without writing any code our blog application is already themeable To use a themewe mainly need to develop the theme by writing customized view files in the theme Forexample to use a theme named classic that uses a different page layout we would createa layout view file wwwrootblogthemesclassicviewslayoutsmainphp We also needto change the application configuration to indicate our choice of the classic theme

return array(

rsquothemersquo=gtrsquoclassicrsquo

)

642 Internationalization

We may also internationalize our blog application so that its pages can be displayed indifferent languages This mainly involves efforts in two aspects

First we may create view files in different languages For example for the list pageof PostController we can create a view file wwwrootblogprotectedviewspostzh cn

listphp When the application is configured to use simplified Chinese (the language codeis zh cn) Yii will automatically use this new view file instead of the original one

Second we may create message translations for those messages generated by code Themessage translations should be saved as files under the directory wwwrootblogprotected

messages We also need to modify the code where we use text strings by enclosing themin the method call Yiit()

For more details about internationalization please refer to the Guide

643 Improving Performance with Cache

While the Yii framework itself is very efficient it is not necessarily true that an applicationwritten in Yii efficient There are several places in our blog application that we can improvethe performance For example the tag clould portlet could be one of the performancebottlenecks because it involves complex database query and PHP logic

52 6 Final Work

We can make use of the sophisticated caching feature provided by Yii to improve theperformance One of the most useful components in Yii is COutputCache which caches afragment of page display so that the underlying code generating the fragment does not needto be executed for every request For example in the layout file wwwrootblogprotected

viewslayoutsmainphp we can enclose the tag cloud portlet with COutputCache

ltphp if($this-gtbeginCache(rsquotagCloudrsquo array(rsquodurationrsquo=gt3600))) gt

ltphp $this-gtwidget(rsquoTagCloudrsquo array(

rsquomaxTagsrsquo=gtYiiapp()-gtparams[rsquotagCloudCountrsquo]

)) gt

ltphp $this-gtendCache() gt

With the above code the tag cloud display will be served from cache instead of beinggenerated on-the-fly for every request The cached content will remain valid in cache for3600 seconds

644 Adding New Features

Our blog application only has very basic functionalities To become a complete blogsystem more features are needed for example calendar portlet email notifications postcategorization archived post portlet and so on We will leave the implementation of thesefeatures to interested readers

  • Contents
  • License
  • Getting Started
    • Building a Blog System using Yii
    • Testdriving with Yii
      • Installing Yii
      • Creating Skeleton Application
      • Application Workflow
        • Requirements Analysis
        • Overall Design
          • Initial Prototyping
            • Setting Up Database
              • Creating Database
              • Establishing Database Connection
                • Scaffolding
                • Authenticating User
                • Summary
                  • Post Management
                    • Customizing Post Model
                      • Customizing rules() Method
                      • Customizing relations() Method
                      • Representing Status in Text
                        • Creating and Updating Posts
                          • Customizing Access Control
                          • Customizing create and update Operations
                            • Displaying Posts
                              • Customizing view Operation
                              • Customizing index Operation
                                • Managing Posts
                                  • Listing Posts in Tabular View
                                  • Deleting Posts
                                      • Comment Management
                                        • Customizing Comment Model
                                          • Customizing rules() Method
                                          • Customizing attributeLabels() Method
                                          • Customizing Saving Process
                                            • Creating and Displaying Comments
                                              • Displaying Comments
                                              • Creating Comments
                                                • Managing Comments
                                                  • Updating and Deleting Comments
                                                  • Approving Comments
                                                      • Portlets
                                                        • Customizing Page Layout
                                                        • Creating User Menu Portlet
                                                          • Creating UserMenu Class
                                                          • Creating userMenu View
                                                          • Using UserMenu Portlet
                                                          • Testing UserMenu Portlet
                                                          • Summary
                                                            • Creating Tag Cloud Portlet
                                                              • Creating TagCloud Class
                                                              • Using TagCloud Portlet
                                                                • Creating Recent Comments Portlet
                                                                  • Creating RecentComments Class
                                                                  • Creating recentComments View
                                                                  • Using RecentComments Portlet
                                                                      • Final Work
                                                                        • Beautifying URLs
                                                                        • Logging Errors
                                                                        • Final Tune-up and Deployment
                                                                          • Changing Home Page
                                                                          • Enabling Schema Caching
                                                                          • Disabling Debugging Mode
                                                                          • Deploying the Application
                                                                            • Future Enhancements
                                                                              • Using a Theme
                                                                              • Internationalization
                                                                              • Improving Performance with Cache
                                                                              • Adding New Features
Page 4: Yii Blog 1.1.0 Demo Tutor

Contents iii

5 Portlets 39

51 Customizing Page Layout 39

52 Creating User Menu Portlet 41

521 Creating UserMenu Class 41

522 Creating userMenu View 42

523 Using UserMenu Portlet 42

524 Testing UserMenu Portlet 43

525 Summary 43

53 Creating Tag Cloud Portlet 43

531 Creating TagCloud Class 43

532 Using TagCloud Portlet 44

54 Creating Recent Comments Portlet 45

541 Creating RecentComments Class 45

542 Creating recentComments View 46

543 Using RecentComments Portlet 46

6 Final Work 47

61 Beautifying URLs 47

62 Logging Errors 48

63 Final Tune-up and Deployment 49

631 Changing Home Page 49

632 Enabling Schema Caching 49

633 Disabling Debugging Mode 50

634 Deploying the Application 50

64 Future Enhancements 51

iv Contents

641 Using a Theme 51

642 Internationalization 51

643 Improving Performance with Cache 51

644 Adding New Features 52

License of Yii

The Yii framework is free software It is released under the terms of the following BSDLicense

Copyright ccopy2008-2009 by Yii Software LLC All rights reserved

Redistribution and use in source and binary forms with or without modification arepermitted provided that the following conditions are met

1 Redistributions of source code must retain the above copyright notice this list ofconditions and the following disclaimer

2 Redistributions in binary form must reproduce the above copyright notice this list ofconditions and the following disclaimer in the documentation andor other materialsprovided with the distribution

3 Neither the name of Yii Software LLC nor the names of its contributors may beused to endorse or promote products derived from this software without specificprior written permission

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS rdquoAS

ISrdquo AND ANY EXPRESS OR IMPLIED WARRANTIES INCLUDING BUT NOT LIMITED TO THE

IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PUR-

POSE ARE DISCLAIMED IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBU-

TORS BE LIABLE FOR ANY DIRECT INDIRECT INCIDENTAL SPECIAL EXEMPLARY OR

CONSEQUENTIAL DAMAGES (INCLUDING BUT NOT LIMITED TO PROCUREMENT OF SUB-

STITUTE GOODS OR SERVICES LOSS OF USE DATA OR PROFITS OR BUSINESS INTERRUP-

TION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY WHETHER IN CONTRACT

STRICT LIABILITY OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY

WAY OUT OF THE USE OF THIS SOFTWARE EVEN IF ADVISED OF THE POSSIBILITY OF

SUCH DAMAGE

vi Contents

Chapter 1

Getting Started

11 Building a Blog System using Yii

This tutorial describes how to use Yii to develop a blog application shown as the blog demowhich can be found in the Yii release files It explains in detail every step to be takenduring the development which may also be applied in developing other Web applicationsAs a complement to the Guide and the Class Reference of Yii this tutorial aims to showpractical usage of Yii instead of thorough and definitive description

Readers of this tutorial are not required to have prior knowledge about Yii However basicknowledge of object-oriented programming (OOP) and database programming would helpreaders to understand the tutorial more easily

This tutorial is released under the Terms of Yii Documentation

12 Testdriving with Yii

In this section we describe how to create a skeleton application that will serve as ourstarting point For simplicity we assume that the document root of our Web server iswwwroot and the corresponding URL is httpwwwexamplecom

121 Installing Yii

We first install the Yii framework Grab a copy of the Yii release file (version 110 orabove) from wwwyiiframeworkcom and unpack it to the directory wwwrootyii Doublecheck to make sure that there is a directory wwwrootyiiframework

Tip The Yii framework can be installed anywhere in the file system not necessarilyunder a Web folder Its framework directory contains all framework code and isthe only framework directory needed when deploying an Yii application A singleinstallation of Yii can be used by multiple Yii applications

2 1 Getting Started

After installing Yii open a browser window and access the URL httpwwwexample

comyiirequirementsindexphp It shows the requirement checker provided in the Yiirelease For our blog application besides the minimal requirements needed by Yii we alsoneed to enable both the pdo and pdo sqlite PHP extensions so that we can access SQLitedatabases

122 Creating Skeleton Application

We then use the yiic tool to create a skeleton application under the directory wwwroot

blog The yiic tool is a command line tool provided in the Yii release It can be used togenerate code to reduce certain repetitive coding tasks

Open a command window and execute the following command

wwwrootyiiframeworkyiic webapp wwwrootblog

Create a Web application under rsquowwwrootblogrsquo [Yes|No]y

Tip In order to use the yiic tool as shown above the CLI PHP program must beon the command search path If not the following command may be used instead

pathtophp wwwrootyiiframeworkyiicphp webapp wwwrootblog

To try out the application we just created open a Web browser and navigate to the URLhttpwwwexamplecomblogindexphp We should see that our skeleton applicationalready has four fully functional pages the homepage the about page the contact pageand the login page

In the following we briefly describe what we have in this skeleton application

Entry Script

We have an entry script file wwwrootblogindexphp which has the following content

ltphp

$yii=rsquowwwrootframeworkyiiphprsquo

$config=dirname( FILE )rsquoprotectedconfigmainphprsquo

remove the following line when in production mode

defined(rsquoYII DEBUGrsquo) or define(rsquoYII DEBUGrsquotrue)

12 Testdriving with Yii 3

require once($yii)

YiicreateWebApplication($config)-gtrun()

This is the only script that Web users can directly access The script first includes theYii bootstrap file yiiphp It then creates an application instance with the specifiedconfiguration and executes the application

Base Application Directory

We also have an application base directory wwwrootblogprotected The majority of ourcode and data will be placed under this directory and it should be protected from beingaccessed by Web users For Apache httpd Web server we place under this directory ahtaccess file with the following content

deny from all

For other Web servers please refer to the corresponding manual on how to protect adirectory from being accessed by Web users

123 Application Workflow

To help understand how Yii works we describe the main workflow in our skeleton appli-cation when a user is accessing its contact page

1 The user requests the URL httpwwwexamplecomblogindexphpr=sitecontact

2 The entry script is executed by the Web server to process the request

3 An application instance is created and configured with initial property values speci-fied in the application configuration file wwwrootblogprotectedconfigmainphp

4 The application resolves the request into a controller and a controller action For thecontact page request it is resolved as the site controller and the contact action (theactionContact method in wwwrootblogprotectedcontrollersSiteControllerphp)

5 The application creates the site controller in terms of a SiteController instanceand then executes it

6 The SiteController instance executes the contact action by calling its actionContact()method

4 1 Getting Started

7 The actionContact method renders a view named contact to the Web user Inter-nally this is achieved by including the view file wwwrootblogprotectedviews

sitecontactphp and embedding the result into the layout file wwwrootblog

protectedviewslayoutsmainphp

13 Requirements Analysis

The blog system that we are going to develop is a single user system The owner of thesystem will be able to perform the following actions

bull Login and logout

bull Create update and delete posts

bull Publish unpublish and archive posts

bull Approve and delete comments

All other users are guest users who can perform the following actions

bull Read posts

bull Create comments

Additional Requirements for this system include

bull The homepage of the system should display a list of the most recent posts

bull If a page contains more than 10 posts they should be displayed in pages

bull The system should display a post together with its comments

bull The system should be able to list posts with a specified tag

bull The system should show a cloud of tags indicating their use frequencies

bull The system should show a list of most recent comments

bull The system should be themeable

bull The system should use SEO-friendly URLs

14 Overall Design 5

14 Overall Design

Based on the analysis of the requirements we decide to use the following database tablesto store the persistent data for our blog application

bull tbl user stores the user information including username and password

bull tbl post stores the blog post information It mainly consists of the following columns

ndash title required title of the post

ndash content required body content of the post which uses the Markdown format

ndash status required status of the post which can be one of following values

lowast 1 meaning the post is in draft and is not visible to public

lowast 2 meaning the post is published to public

lowast 3 meaning the post is outdated and is not visible in the post list (stillaccessible individually though)

ndash tags optional a list of comma-separated words categorizing the post

bull tbl comment stores the post comment information Each comment is associated witha post and mainly consists of the following columns

ndash name required the author name

ndash email required the author email

ndash website optional the author website URL

ndash content required the comment content in plain text format

ndash status required status of the comment which indicates whether the commentis approved (value 2) or not (value 1)

bull tbl tag stores post tag frequency information that is needed to implement the tagcloud feature The table mainly contains the following columns

ndash name required the unique tag name

ndash frequency required the number of times that the tag appears in posts

bull tbl lookup stores generic lookup information It is essentially a map between integervalues and text strings The former is the data representation in our code while thelatter is the corresponding presentation to end users For example we use integer1 to represent the draft post status and string Draft to display this status to endusers This table mainly contains the following columns

6 1 Getting Started

ndash name the textual representation of the data item that is to be displayed to endusers

ndash code the integer representation of the data item

ndash type the type of the data item

ndash position the relative display order of the data item among other items of thesame type

The following entity-relation (ER) diagram shows the table structure and relationshipsabout the above tables

Figure 11 Entity-Relation Diagram of the Blog Database

Complete SQL statements corresponding to the above ER diagram may be found inthe blog demo In our Yii installation they are in the file wwwrootyiidemosblog

protecteddataschemasqlitesql

Info We name all our table names and column names in lower case This is becausedifferent DBMS often have different case-sensitivity treatment and we want to avoidtroubles like this

We also prefix all our tables with tbl This serves for two purposes First the prefixintroduces a namespace to these tables in case when they need to coexist with othertables in the same database which often happens in a shared hosting environmentwhere a single database is being used by multiple applications Second using tableprefix reduces the possibility of having some table names that are reserved keywordsin DBMS

14 Overall Design 7

We divide the development of our blog application into the following milestones

bull Milestone 1 creating a prototype of the blog system It should consist of most ofthe required functionalities

bull Milestone 2 completing post management It includes creating listing showingupdating and deleting posts

bull Milestone 3 completing comment management It includes creating listing ap-proving updating and deleting post comments

bull Milestone 4 implementing portlets It includes user menu login tag cloud andrecent comments portlets

bull Milestone 5 final tune-up and deployment

8 1 Getting Started

Chapter 2

Initial Prototyping

21 Setting Up Database

Having created a skeleton application and finished the database design in this section wewill create the blog database and establish the connection to it in the skeleton application

211 Creating Database

We choose to create a SQLite database Because the database support in Yii is built on topof PDO we can easily switch to use a different type of DBMS (eg MySQL PostgreSQL)without the need to change our application code

We create the database file blogdb under the directory wwwrootblogprotecteddataNote that both the directory and the database file have to be writable by the Web serverprocess as required by SQLite We may simply copy the database file from the blogdemo in our Yii installation which is located at wwwrootyiidemosblogprotecteddatablogdb We may also generate the database by executing the SQL statements in the filewwwrootyiidemosblogprotecteddataschemasqlitesql

Tip To execute SQL statements we may use the sqlite3 command line tool thatcan be found in the SQLite official website

212 Establishing Database Connection

To use the blog database in the skeleton application we created we need to modify itsapplication configuration which is stored in the PHP script wwwrootblogprotected

configmainphp The script returns an associative array consisting of name-value pairseach of which is used to initialize a writable property of the application instance

We configure the db component as follows

10 2 Initial Prototyping

return array(

rsquocomponentsrsquo=gtarray(

rsquodbrsquo=gtarray(

rsquoconnectionStringrsquo=gtrsquosqlitewwwrootblogprotecteddatablogdbrsquo

rsquotablePrefixrsquo=gtrsquotbl rsquo

)

)

)

The above configuration says that we have a db application component whose connectionStringproperty should be initialized as sqlitewwwrootblogprotecteddatablogdb and whosetablePrefix property should be tbl

With this configuration we can access the DB connection object using Yiiapp()-gtdb

at any place in our code Note that Yiiapp() returns the application instance that wecreate in the entry script If you are interested in possible methods and properties that theDB connection has you may refer to its class reference However in most cases we are notgoing to use this DB connection directly Instead we will use the so-called ActiveRecordto access the database

We would like to explain a bit more about the tablePrefix property that we set in theconfiguration This tells the db connection that it should respect the fact we are usingtbl as the prefix to our database table names In particular if in a SQL statement thereis a token enclosed within double curly brackets (eg post then the db connectionshould translate it into a name with the table prefix (eg tbl post) before sending it toDBMS for execution This feature is especially useful if in future we need to modify thetable name prefix without touching our source code For example if we are developing ageneric content management system (CMS) we may exploit this feature so that when itis being installed in a new environment we can allow users to choose a table prefix theylike

22 Scaffolding 11

Tip If you want to use MySQL instead of SQLite to store data you may cre-ate a MySQL database named blog using the SQL statements in wwwrootyii

demosblogprotecteddataschemamysqlsql Then modify the applicationconfiguration as follows

return array(

rsquocomponentsrsquo=gtarray(

rsquodbrsquo=gtarray(

rsquoconnectionStringrsquo =gt rsquomysqlhost=localhostdbname=blogrsquo

rsquoemulatePreparersquo =gt true

rsquousernamersquo =gt rsquorootrsquo

rsquopasswordrsquo =gt rsquorsquo

rsquocharsetrsquo =gt rsquoutf8rsquo

rsquotablePrefixrsquo =gt rsquotbl rsquo

)

)

)

22 Scaffolding

Create read update and delete (CRUD) are the four basic operations of data objects in anapplication Because the task of implementing the CRUD operations is so common whendeveloping Web applications Yii provides a tool to automate this process (also known asscaffolding) In this section we will describe how to use the tool to implement CRUD forposts and comments

Open a command window and run the following commands

wwwrootyiiframeworkyiic shell wwwrootblogindexphp

Yii Interactive Tool v11

Please type rsquohelprsquo for help Type rsquoexitrsquo to quit

gtgt model

gtgt crud Post

gtgt crud Comment

gtgt exit

12 2 Initial Prototyping

Info Some PHP installations may use a different phpini file for command line(CLI) PHP parser As a result when running the above yiic commands youmay encounter errors like rdquoYiiBaseinclude(PDOphp) failed to open streamrdquoor rdquocould not find driverrdquo Please double check your CLI PHP configuration byexecuting the following command

php -r phpinfo()

The result of the above command will show which phpini file is being used andwhich extensions are loaded If a wrong phpini file is used you may use thefollowing command to explicitly specify the correct phpini to use

php -c phpini wwwrootyiiframeworkyiicphp shell wwwrootblogindexphp

The commands above accomplish two tasks First the model command generates a modelclass file for each table in the blog database Second the crud commands generate thecode needed by the CRUD operations for the Post and Comment models

Tip The command model generates a model class for every table in the databaseSometimes we probably donrsquot want to do so (eg the database contains some irrel-evant tables) In this case we can create model classes one by one For exampleto create the User model we can use the command model User The model com-mand also has some more advanced usages For more details execute help model

command

We can test the generated code by accessing the following URLs

httpwwwexamplecomblogindexphpr=post

httpwwwexamplecomblogindexphpr=comment

Notice that the post and comment features implemented by the generated code are com-pletely independent of each other Also when creating a new post or comment we arerequired to enter information such as author id and create time which in real applica-tion should be set by the program Donrsquot worry We will fix these problems in the nextmilestones For now we should be fairly satisfied as this prototype already contains mostfeatures that we need to implement for the blog application

To prepare for the next milestones letrsquos take a closer look at the files generated by theabove commands All the files are generated under wwwrootblogprotected For conve-nience we group them into model files controller files and view files

22 Scaffolding 13

bull model files

ndash modelsUserphp contains the User class that extends from CActiveRecord andcan be used to access the tbl user database table

ndash modelsPostphp contains the Post class that extends from CActiveRecord andcan be used to access the tbl post database table

ndash modelsTagphp contains the Tag class that extends from CActiveRecord andcan be used to access the tbl tag database table

ndash modelsCommentphp contains the Comment class that extends from CActiveRe-cord and can be used to access the tbl comment database table

ndash modelsLookupphp contains the Lookup class that extends from CActiveRecordand can be used to access the tbl lookup database table

bull controller file

ndash controllersPostControllerphp contains the PostController class which is thecontroller in charge of all CRUD operations about posts

ndash controllersCommentControllerphp contains the CommentController class whichis the controller in charge of all CRUD operations about comments

bull view files

ndash viewspostcreatephp is the view file that shows an HTML form to create anew post

ndash viewspostupdatephp is the view file that shows an HTML form to update anexisting post

ndash viewspostviewphp is the view file that displays the detailed information of apost

ndash viewspostindexphp is the view file that displays a list of posts

ndash viewspostadminphp is the view file that displays posts in a table with ad-ministrative commands

ndash viewspost formphp is the partial view file embedded in viewspostcreate

php and viewspostupdatephp It displays the HTML form for collecting postinformation

ndash viewspost viewphp is the partial view file used by viewspostindexphp Itdisplays the brief view of a single post

ndash a similar set of view files are also generated for comment

14 2 Initial Prototyping

In order to understand better how the above files are used we show in the following theworkflow that occurs in the blog application when displaying a list of posts

1 The user requests the URL httpwwwexamplecomblogindexphpr=posts

2 The entry script is executed by the Web server which creates and initializes anapplication instance to handle the request

3 The application creates an instance of PostController and executes it

4 The PostController instance executes the index action by calling its actionIndex()

method Note that index is the default action if the user does not specify an actionto execute in the URL

5 The actionIndex() method queries database to bring back the list of recent posts

6 The actionIndex() method renders the index view with the post data

23 Authenticating User

Our blog application needs to differentiate between the system owner and guest usersTherefore we need to implement the user authentication feature

As you may have found that the skeleton application already provides user authenticationby checking if the username and password are both demo or admin In this section wewill modify the corresponding code so that the authentication is done against the User

database table

User authentication is performed in a class implementing the IUserIdentity interface Theskeleton application uses the UserIdentity class for this purpose The class is stored inthe file wwwrootblogprotectedcomponentsUserIdentityphp

Tip By convention the name of a class file must be the same as the correspondingclass name suffixed with the extension php Following this convention one canrefer to a class using a path alias For example we can refer to the UserIdentity

class with the alias applicationcomponentsUserIdentity Many APIs in Yiican recognize path aliases (eg YiicreateComponent()) and using path aliasesavoids the necessity of embedding absolute file paths in the code The existence ofthe latter often causes trouble when we deploy an application

We modify the UserIdentity class as follows

23 Authenticating User 15

ltphp

class UserIdentity extends CUserIdentity

private $ id

public function authenticate()

$username=strtolower($this-gtusername)

$user=Usermodel()-gtfind(rsquoLOWER(username)=rsquoarray($username))

if($user===null)

$this-gterrorCode=selfERROR USERNAME INVALID

else if($user-gtvalidatePassword($this-gtpassword))

$this-gterrorCode=selfERROR PASSWORD INVALID

else

$this-gt id=$user-gtid

$this-gtusername=$user-gtusername

$this-gterrorCode=selfERROR NONE

return $this-gterrorCode==selfERROR NONE

public function getId()

return $this-gt id

In the authenticate() method we use the User class to look for a row in the User tablewhose username column is the same as the given username in a case-insensitive mannerRemember that the User class was created using the yiic tool in the prior section Becausethe User class extends from CActiveRecord we can exploit the ActiveRecord feature toaccess the User table in an OOP fashion

In order to check if the user has entered a valid password we invoke the validatePassword

method of the User class We need to modify the file wwwrootblogprotectedmodels

Userphp as follows Note that instead of storing the plain password in the database westore the hash result of the password and a randomly generated salt key When validatingthe user-entered password we should compare the hash results instead

class User extends CActiveRecord

public function validatePassword($password)

return $this-gthashPassword($password$this-gtsalt)===$this-gtpassword

16 2 Initial Prototyping

public function hashPassword($password$salt)

return md5($salt$password)

In the UserIdentity class we also override the getId() method which returns the id valueof the user found in the User table The parent implementation would return the usernameinstead Both the username and id properties will be stored in the user session and maybe accessed via Yiiapp()-gtuser from anywhere in our code

Tip In the UserIdentity class we reference the class CUserIdentity without ex-plicitly including the corresponding class file This is because CUserIdentity is acore class provided by the Yii framework Yii will automatically include the classfile for any core class when it is referenced for the first time

We also do the same with the User class This is because the User class file is placedunder the directory wwwrootblogprotectedmodels which has been added tothe PHP include path according to the following lines found in the applicationconfiguration

return array(

rsquoimportrsquo=gtarray(

rsquoapplicationmodelsrsquo

rsquoapplicationcomponentsrsquo

)

)

The above configuration says that any class whose class file is located under either wwwrootblogprotectedmodels or wwwrootblogprotectedcomponents willbe automatically included when the class is referenced for the first time

The UserIdentity class is mainly used by the LoginForm class to authenticate a user basedon the username and password input collected from the login page The following codefragment shows how UserIdentity is used

$identity=new UserIdentity($username$password)

$identity-gtauthenticate()

switch($identity-gterrorCode)

24 Summary 17

case UserIdentityERROR NONE

Yiiapp()-gtuser-gtlogin($identity)

break

Info People often get confused about identity and the user application componentThe former represents a way of performing authentication while the latter is usedto represent the information related with the current user An application can onlyhave one user component but it can have one or several identity classes dependingon what kind of authentication it supports Once authenticated an identity instancemay pass its state information to the user component so that they are globallyaccessible via user

To test the modified UserIdentity class we can browse the URL httpwwwexample

comblogindexphp and try logging in with the username and password that we store inthe User table If we use the database provided by the blog demo we should be able tologin with username demo and password demo Note that this blog system does not providethe user management feature As a result a user cannot change his account or create anew one through the Web interface The user management feature may be considered asa future enhancement to the blog application

24 Summary

We have completed the milestone 1 Letrsquos summarize what we have done so far

1 We identified the requirements to be fulfilled

2 We installed the Yii framework

3 We created a skeleton application

4 We designed and created the blog database

5 We modified the application configuration by adding the database connection

6 We generated the code that implements the basic CRUD operations for both postsand comments

7 We modified the authentication method to check against the tbl user table

18 2 Initial Prototyping

For a new project most of the time will be spent in step 1 and 4 for this first milestone

Although the code generated by the yiic tool implements fully functional CRUD oper-ations for a database table it often needs to be modified in practical applications Forthis reason in the next two milestone our job is to customize the generated CRUD codeabout posts and comments so that it reaches our initial requirements

In general we first modify the model class file by adding appropriate validation rules anddeclaring relational objects We then modify the controller action and view code for eachindividual CRUD operation

Chapter 3

Post Management

31 Customizing Post Model

The Post model class generated by the yiic tool mainly needs to be modified in two places

bull the rules() method specifies the validation rules for the model attributes

bull the relations() method specifies the related objects

Info A model consists of a list of attributes each associated with a column in thecorresponding database table Attributes can be declared explicitly as class membervariables or implicitly without any declaration

311 Customizing rules() Method

We first specify the validation rules which ensure the attribute values entered by usersare correct before they are saved to the database For example the status attribute ofPost should be an integer 1 2 or 3 The yiic tool also generates validation rules for eachmodel However these rules are based on the table column information and may not beappropriate

Based on the requirement analysis we modify the rules() method as follows

public function rules()

return array(

array(rsquotitle content statusrsquo rsquorequiredrsquo)

array(rsquotitlersquo rsquolengthrsquo rsquomaxrsquo=gt128)

array(rsquostatusrsquo rsquoinrsquo rsquorangersquo=gtarray(123))

array(rsquotagsrsquo rsquomatchrsquo rsquopatternrsquo=gtrsquo^[ws]+$rsquorsquomessagersquo=gtrsquoTags can only contain word charactersrsquo)

20 3 Post Management

array(rsquotagsrsquo rsquonormalizeTagsrsquo)

)

In the above we specify that the title content and status attributes are required thelength of title should not exceed 128 the status attribute value should be 1 (draft) 2(published) or 3 (archived) and the tags attribute should only contain word charactersand commas In addition we use normalizeTags to normalize the user-entered tags so thatthe tags are unique and properly separated with commas

The validators such as required length in and match are all built-in validators providedby Yii The normalizeTags validator is a method-based validator that we need to definein the Post class For more information about how to specify validation rules please referto the Guide

public function normalizeTags($attribute$params)

$this-gttags=Tagarray2string(array unique(Tagstring2array($this-gttags)))

The rules declared in this method are executed one by one when we call the validate() orsave() method of the model instance

Note It is very important to remember that attributes appearing in rules() mustbe those to be entered by end users Other attributes such as id and create time

in the Post model which are set by our code or database should not be in rules()For more details please refer to Securing Attribute Assignments

After making these changes we can visit the post creation page again to verify that thenew validation rules are taking effect

312 Customizing relations() Method

Lastly we customize the relations() method to specify the related objects of a post Bydeclaring these related objects in relations() we can exploit the powerful RelationalActiveRecord (RAR) feature to access the related object information of a post such asits author and comments without the need to write complex SQL JOIN statements

We customize the relations() method as follows

31 Customizing Post Model 21

public function relations()

return array(

rsquoauthorrsquo =gt array(selfBELONGS TO rsquoUserrsquo rsquoauthor idrsquo)

rsquocommentsrsquo =gt array(selfHAS MANY rsquoCommentrsquo rsquopost idrsquo

rsquoconditionrsquo=gtrsquocommentsstatus=rsquoCommentSTATUS APPROVED

rsquoorderrsquo=gtrsquocommentscreate time DESCrsquo)

rsquocommentCountrsquo =gt array(selfSTAT rsquoCommentrsquo rsquopost idrsquo

rsquoconditionrsquo=gtrsquostatus=rsquoCommentSTATUS APPROVED)

)

The above relations state that

bull A post belongs to an author whose class is User and the relationship is establishedbased on the author id attribute value of the post

bull A post has many comments whose class is Comment and the relationship is establishedbased on the post id attribute value of the comments These comments should besorted according to their creation time and the comments must be approved

bull The commentCount relation is a bit special as it returns back an aggregation resultwhich is about how many comments the post has

With the above relation declaration we can easily access the author and comments of apost like the following

$author=$post-gtauthor

echo $author-gtusername

$comments=$post-gtcomments

foreach($comments as $comment)

echo $comment-gtcontent

For more details about how to declare and use relations please refer to the Guide

313 Representing Status in Text

Because the status of a post is stored as an integer in the database we need to provide atextual representation so that it is more intuitive when being displayed to end users In alarge system the similar requirement is very common

22 3 Post Management

As a generic solution we use the tbl lookup table to store the mapping between integervalues and textual representations that are needed by other data objects We modify theLookup model class as follows to more easily access the textual data in the table

class Lookup extends CActiveRecord

private static $ items=array()

public static function items($type)

if(isset(self$ items[$type]))

selfloadItems($type)

return self$ items[$type]

public static function item($type$code)

if(isset(self$ items[$type]))

selfloadItems($type)

return isset(self$ items[$type][$code]) self$ items[$type][$code] false

private static function loadItems($type)

self$ items[$type]=array()

$models=selfmodel()-gtfindAll(array(

rsquoconditionrsquo=gtrsquotype=typersquo

rsquoparamsrsquo=gtarray(rsquotypersquo=gt$type)

rsquoorderrsquo=gtrsquopositionrsquo

))

foreach($models as $model)

self$ items[$type][$model-gtcode]=$model-gtname

Our new code mainly provides two static methods Lookupitems() and Lookupitem()The former returns a list of strings belonging to the specified data type while the latterreturns a particular string for the given data type and data value

Our blog database is pre-populated with two lookup types PostStatus and CommentStatusThe former refers to the possible post statuses while the latter the comment statuses

In order to make our code easier to read we also declare a set of constants to representthe status integer values We should use these constants through our code when referringto the corresponding status values

32 Creating and Updating Posts 23

class Post extends CActiveRecord

const STATUS DRAFT=0

const STATUS PUBLISHED=1

const STATUS ARCHIVED=2

Therefore we can call Lookupitems(rsquoPostStatusrsquo) to get the list of possible post statuses(text strings indexed by the corresponding integer values) and call Lookupitem(rsquoPostStatusrsquoPostSTATUS PUBLISHED) to get the string representation of the published status

32 Creating and Updating Posts

With the Post model ready we need to fine-tune the actions and views for the controllerPostController In this section we first customize the access control of CRUD operationswe then modify the code implementing the create and update operations

321 Customizing Access Control

The first thing we want to do is to customize the access control because the code generatedby yiic does not fit our needs

We modify the accessRules() method in the file wwwrootblogprotectedcontrollers

PostControllerphp as follows

public function accessRules()

return array(

array(rsquoallowrsquo allow all users to perform rsquolistrsquo and rsquoshowrsquo actions

rsquoactionsrsquo=gtarray(rsquoindexrsquo rsquoviewrsquo)

rsquousersrsquo=gtarray(rsquorsquo)

)

array(rsquoallowrsquo allow authenticated users to perform any action

rsquousersrsquo=gtarray(rsquorsquo)

)

array(rsquodenyrsquo deny all users

rsquousersrsquo=gtarray(rsquorsquo)

)

)

The above rules state that all users can access the index and view actions and authenti-cated users can access any actions including the admin action The user should be denied

24 3 Post Management

access in any other scenario Note that these rules are evaluated in the order they arelisted here The first rule matching the current context makes the access decision Forexample if the current user is the system owner who tries to visit the post creation pagethe second rule will match and it will give the access to the user

322 Customizing create and update Operations

The create and update operations are very similar They both need to display an HTMLform to collect user inputs validate them and save them into database The main differ-ence is that the update operation will pre-populate the form with the existing post datafound in the database For this reason the yiic tool generates a partial view wwwroot

blogprotectedviewspost formphp that is embedded in both the create and update

views to render the needed HTML form

We first change the formphp file so that the HTML form only collects the inputs wewant title content tags and status We use plain text fields to collect inputs for thefirst three attributes and a dropdown list to collect input for status The dropdown listoptions are the text displays of the possible post statuses

ltphp echo CHtmlactiveDropDownList($postrsquostatusrsquoLookupitems(rsquoPostStatusrsquo)) gt

In the above we call Lookupitems(rsquoPostStatusrsquo) to bring back the list of post statuses

We then modify the Post class so that it can automatically set some attributes (eg create

time author id) before a post is saved to the database We override the beforeSave()

method as follows

protected function beforeSave()

if(parentbeforeSave())

if($this-gtisNewRecord)

$this-gtcreate time=$this-gtupdate time=time()

$this-gtauthor id=Yiiapp()-gtuser-gtid

else

$this-gtupdate time=time()

return true

else

return false

33 Displaying Posts 25

When we save a post we want to update the tbl tag table to reflect the change of tagfrequencies We can do this work in the afterSave() method which is automaticallyinvoked by Yii after a post is successfully saved into the database

protected function afterSave()

parentafterSave()

Tagmodel()-gtupdateFrequency($this-gt oldTags $this-gttags)

private $ oldTags

protected function afterFind()

parentafterFind()

$this-gt oldTags=$this-gttags

In the implementation because we want to detect if the user changes the tags in case he isupdating an existing post we need to know what the old tags are For this reason we alsowrite the afterFind() method to keep the old tags in the variable oldTags The methodafterFind() is invoked automatically by Yii when an AR record is populated with thedata from database

We are not going to give details of the TagupdateFrequency() method here Interestedreaders may refer to the file wwwrootyiidemosblogprotectedmodelsTagphp

33 Displaying Posts

In our blog application a post may be displayed among a list of posts or by itself Theformer is implemented as the index operation while the latter the view operation In thissection we customize both operations to fulfill our initial requirements

331 Customizing view Operation

The view operation is implemented by the actionView() method in PostController Itsdisplay is generated by the view view with the view file wwwrootblogprotectedviews

postviewphp

Below is the relevant code implementing the view operation in PostController

public function actionView()

26 3 Post Management

$this-gtrender(rsquoviewrsquoarray(

$post=$this-gtloadModel()

$this-gtrender(rsquoviewrsquoarray(

rsquomodelrsquo=gt$post

))

))

private $ model

public function loadModel()

if($this-gt model===null)

if(isset($ GET[rsquoidrsquo]))

if(Yiiapp()-gtuser-gtisGuest)

$condition=rsquostatus=rsquoPostSTATUS PUBLISHED

rsquo OR status=rsquoPOSTSTATUS ARCHIVED

else

$condition=rsquorsquo

$this-gt model=Postmodel()-gtfindbyPk($ GET[rsquoidrsquo] $condition)

if($this-gt model===null)

throw new CHttpException(404rsquoThe requested page does not existrsquo)

return $this-gt model

Our change mainly lies in the loadModel() method In this method we query the Post

table according to the id GET parameter If the post is not found or if it is not publishedor archived (when the user is a guest) we will throw a 404 HTTP error Otherwise thepost object is returned to actionView() which in turn passes the post object to the viewscript for further display

Tip Yii captures HTTP exceptions (instances of CHttpException) and displaysthem in either predefined templates or customized error views The skeleton appli-cation generated by yiic already contains a customized error view in wwwroot

blogprotectedviewssiteerrorphp We can modify this file if we want tofurther customize the error display

The change in the view script is mainly about ajdusting the formatting and styles of thepost display We will not go into details here Interested readers may refer to wwwroot

blogprotectedviewspostviewphp

33 Displaying Posts 27

332 Customizing index Operation

Like the view operation we customize the index operation in two places the actionIndex()

method in PostController and the view file wwwrootblogprotectedviewspostindex

php We mainly need to add the support for displaying a list of posts that are associatedwith a specified tag

Below is the modified actionIndex() method in PostController

public function actionIndex()

$criteria=new CDbCriteria(array(

rsquoconditionrsquo=gtrsquostatus=rsquoPostSTATUS PUBLISHED

rsquoorderrsquo=gtrsquoupdate time DESCrsquo

rsquowithrsquo=gtrsquocommentCountrsquo

))

if(isset($ GET[rsquotagrsquo]))

$criteria-gtaddSearchCondition(rsquotagsrsquo$ GET[rsquotagrsquo])

$dataProvider=new CActiveDataProvider(rsquoPostrsquo array(

rsquopaginationrsquo=gtarray(

rsquopageSizersquo=gtYiiapp()-gtparams[rsquopostsPerPagersquo]

)

rsquocriteriarsquo=gt$criteria

))

$this-gtrender(rsquoindexrsquoarray(

rsquodataProviderrsquo=gt$dataProvider

))

In the above we first create a query criteria for retrieving post list The criteria statesthat only published posts should be returned and they should be sorted according to theirupdate time in descending order Because when displaying a post in the list we want toshow how many comments the post has received in the criteria we also specify to bringback commentCount which if you remember is a relation declared in Postrelations()

In case when a user wants to see posts with a specific tag we would add a search conditionto the criteria to look for the specified tag

Using the query criteria we create a data provider which mainly serves for three purposesFirst it does pagination of the data when too many results may be returned Herewe customize the pagination by setting the page size to be an application parameterpostsPerPage Second it does sorting according to the user request And finally it feedsthe paginated and sorted data to widgets or view code for presentation

28 3 Post Management

After we finish with actionIndex() we modify the index view as follows Our change ismainly about adding the h1 header when the user specifies to display posts with a tag

ltphp if(empty($ GET[rsquotagrsquo])) gt

lth1gtPosts Tagged with ltigtltphp echo CHtmlencode($ GET[rsquotagrsquo]) gtltigtlth1gt

ltphp endif gt

ltphp $this-gtwidget(rsquoziiwidgetsCListViewrsquo array(

rsquodataProviderrsquo=gt$dataProvider

rsquoitemViewrsquo=gtrsquo viewrsquo

rsquotemplatersquo=gtitemsnpager)) gt

Note that in the above we use [CListView] to display the post list This widget requires apartial view to display the detail of each individual post Here we specify the partial viewto be view which means the file wwwrootblogprotectedviewspost viewphp In thisview script we can acccess the post instance being displayed via a local variable named$data

34 Managing Posts

Managing posts mainly refers to listing posts in an administrative view that allows us tosee posts with all statuses updating them and deleting them They are accomplished bythe admin operation and the delete operation respectively The code generated by yiic

does not need much modification Below we mainly explain how these two operations areimplemented

341 Listing Posts in Tabular View

The admin operation shows posts with all statuses in a tabular view The view supportssorting and pagination The following is the actionAdmin() method in PostController

public function actionAdmin()

$dataProvider=new CActiveDataProvider(rsquoPostrsquo array(

rsquosortrsquo=gtarray(

rsquodefaultOrderrsquo=gtrsquostatus update time DESCrsquo

)

))

$this-gtrender(rsquoadminrsquoarray(

rsquodataProviderrsquo=gt$dataProvider

))

34 Managing Posts 29

The above code is very similar to that in actionIndex() The main difference is in thequery criteria Here we do not specify any particular query criteria since we want to seeall posts We do customize the sort property of the data provider so that by default theposts are shown in the order of their statuses and update time

Below is the code for the admin view

lth1gtManage Postslth1gt

ltphp $this-gtwidget(rsquoziiwidgetsgridCGridViewrsquo array(

rsquodataProviderrsquo=gt$dataProvider

rsquocolumnsrsquo=gtarray(

array(

rsquonamersquo=gtrsquotitlersquo

rsquotypersquo=gtrsquorawrsquo

rsquovaluersquo=gtrsquoCHtmllink(CHtmlencode($data-gttitle) $data-gturl)rsquo

)

array(

rsquonamersquo=gtrsquostatusrsquo

rsquovaluersquo=gtrsquoLookupitem(PostStatus$data-gtstatus)rsquo

)

rsquocreate timedatetimersquo

rsquoupdate timedatetimersquo

array(

rsquoclassrsquo=gtrsquoCButtonColumnrsquo

)

)

)) gt

We use [CGridView] to display the posts It allows us to sort by a column and paginatethrough the posts if there are too many to be displayed in a single page Our change ismainly about how to display each column For example for the title column we specifythat it should be displayed as a hyperlink that points to the detailed view of the post

Tip When displaying text we call CHtmlencode() to encode HTML entities init This prevents from cross-site scripting attack

342 Deleting Posts

In the admin data grid there is a delete button in each row Clicking on the button shoulddelete the corresponding post Internally this triggers the delete action implemented asfollows

30 3 Post Management

public function actionDelete()

if(Yiiapp()-gtrequest-gtisPostRequest)

we only allow deletion via POST request

$this-gtloadModel()-gtdelete()

if(isset($ POST[rsquoajaxrsquo]))

$this-gtredirect(array(rsquoindexrsquo))

else

throw new CHttpException(400rsquoInvalid request Please do not repeat this request againrsquo)

The above code is the one generated by the yiic tool without any change We wouldlike to explain a little bit more about the checking on $ POST[rsquoajaxrsquo] The [CGridView]widget has a very nice feature that its sorting pagination and deletion operations are alldone in AJAX mode by default That means the whole page does not get reloaded if anyof the above operations is performed However it is also possible that the widget runs innon-AJAX mode (by setting its ajaxUpdate property to be false or disabling JavaScript onthe client side) It is necessary for the delete action to differentiate these two scenarios ifthe delete request is made via AJAX we should not redirect the user browser otherwisewe should

Deleting a post should also cause the deletion of all comments for that post In additionwe should also update the tbl tag table regarding the tags for the deleted post Both ofthese tasks can be achieved by writing an afterDelete method in the Post model class asfollows

protected function afterDelete()

parentafterDelete()

Commentmodel()-gtdeleteAll(rsquopost id=rsquo$this-gtid)

Tagmodel()-gtupdateFrequency($this-gttags rsquorsquo)

The above code is very straightforward it first deletes all those comments whose post id

is the same as the ID of the deleted post it then updates the tbl tag table for the tags

of the deleted post

34 Managing Posts 31

Tip We have to explicitly delete all comments for the deleted post here becauseSQLite does not really support foreign key constraints In a DBMS that supportsthis constraint (such as MySQL PostgreSQL) the foreign key constraint can be setup such that the DBMS automatically deletes the related comments if the post isdeleted In that case we no longer this explicit deletion call in our code

32 3 Post Management

Chapter 4

Comment Management

41 Customizing Comment Model

For the Comment model we mainly need to customize the rules() and attributeLabels()

methods The attributeLabels() method returns a mapping between attribute namesand attribute labels We do not need to touch relations() since the code generated bythe yiic tool is good enough

411 Customizing rules() Method

We first customize the validation rules generated by the yiic tool The following rules areused for comments

public function rules()

return array(

array(rsquocontent author emailrsquo rsquorequiredrsquo)

array(rsquoauthor email urlrsquo rsquolengthrsquo rsquomaxrsquo=gt128)

array(rsquoemailrsquorsquoemailrsquo)

array(rsquourlrsquorsquourlrsquo)

)

In the above we specify that the author email and content attributes are required thelength of author email and url cannot exceed 128 the email attribute must be a validemail address and the url attribute must be a valid URL

412 Customizing attributeLabels() Method

We then customize the attributeLabels() method to declare the label display for eachmodel attribute This method returns an array consisting of name-label pairs When wecall CHtmlactiveLabel() to display an attribute label

34 4 Comment Management

public function attributeLabels()

return array(

rsquoidrsquo =gt rsquoIdrsquo

rsquocontentrsquo =gt rsquoCommentrsquo

rsquostatusrsquo =gt rsquoStatusrsquo

rsquocreate timersquo =gt rsquoCreate Timersquo

rsquoauthorrsquo =gt rsquoNamersquo

rsquoemailrsquo =gt rsquoEmailrsquo

rsquourlrsquo =gt rsquoWebsitersquo

rsquopost idrsquo =gt rsquoPostrsquo

)

Tip If the label for an attribute is not declared in attributeLabels() an algo-rithm will be used to generate an appropriate label For example a label VerifyCode will be generated for attributes verify code or verifyCode

413 Customizing Saving Process

Because we want to record the creation time of a comment we override the beforeSave()

method of Comment like we do for the Post model

protected function beforeSave()

if(parentbeforeSave())

if($this-gtisNewRecord)

$this-gtcreate time=time()

return true

else

return false

42 Creating and Displaying Comments

In this section we implement the comment display and creation features

421 Displaying Comments

Instead of displaying and creating comments on individual pages we use the post detailpage (generated by the view action of PostController) Below the post content display

42 Creating and Displaying Comments 35

we display first a list of comments belonging to that post and then a comment creationform

In order to display comments on the post detail page we modify the view script wwwrootblogprotectedviewspostviewphp as follows

ltdiv id=commentsgt

ltphp if($model-gtcommentCountgt=1) gt

lth3gt

ltphp echo $model-gtcommentCount rsquocomment(s)rsquo gt

lth3gt

ltphp $this-gtrenderPartial(rsquo commentsrsquoarray(

rsquopostrsquo=gt$model

rsquocommentsrsquo=gt$model-gtcomments

)) gt

ltphp endif gt

ltdivgt

In the above we call renderPartial() to render a partial view named comments to displaythe list of comments belonging to the current post Note that in the view we use theexpression $model-gtcomments to retrieve the comments for the post This is valid becausewe have declared a comments relation in the Post class Evaluating this expression wouldtrigger an implicit JOIN database query to bring back the proper comments This featureis known as lazy relational query

The partial view comments is not very interesting It mainly goes through every commentand displays the detail of it Interested readers may refer to wwwrootyiidemosblog

protectedpost commentsphp

422 Creating Comments

To handle comment creation we first modify the actionView() method of PostController

as follows

public function actionView()

$post=$this-gtloadModel()

$comment=$this-gtnewComment($post)

$this-gtrender(rsquoviewrsquoarray(

rsquomodelrsquo=gt$post

rsquocommentrsquo=gt$comment

))

36 4 Comment Management

protected function newComment($post)

$comment=new Comment

if(isset($ POST[rsquoCommentrsquo]))

$comment-gtattributes=$ POST[rsquoCommentrsquo]

if($post-gtaddComment($comment))

if($comment-gtstatus==CommentSTATUS PENDING)

Yiiapp()-gtuser-gtsetFlash(rsquocommentSubmittedrsquorsquoThank yoursquo)

$this-gtrefresh()

return $comment

In the above we call the newComment() method before we render view In the newComment()

method we generate a Comment instance and check if the comment form is submitted If sowe try to add the comment for the post by calling $post-gtaddComment($comment) If it goesthrough we refresh the post detail page In case the comment needs to be approved we willshow a flash message to indicate this decision A flash message is usually a confirmationmessage displayed to end users If the user clicks on the refresh button of his browser themessage will disappear

We also need to modify wwwrootblogprotectedviewspostviewphp furthermore

ltdiv id=commentsgt

lth3gtLeave a Commentlth3gt

ltphp if(Yiiapp()-gtuser-gthasFlash(rsquocommentSubmittedrsquo)) gt

ltdiv class=successgt

ltphp echo Yiiapp()-gtuser-gtgetFlash(rsquocommentSubmittedrsquo) gt

ltdivgt

ltphp else gt

ltphp $this-gtrenderPartial(rsquocomment formrsquoarray(

rsquomodelrsquo=gt$comment

)) gt

ltphp endif gt

ltdivgtlt-- comments --gt

43 Managing Comments 37

In the above code we display the flash message if it is available If not we displaythe comment input form by rendering the partial view wwwrootblogprotectedviews

comment formphp

43 Managing Comments

Comment management includes updating deleting and approving comments These op-erations are implemented as actions in the CommentController class

431 Updating and Deleting Comments

The code generated by yiic for updating and deleting comments remains largely un-changed

432 Approving Comments

When comments are newly created they are in pending approval status and need to beapproved in order to be visible to guest users Approving a comment is mainly aboutchanging the status column of the comment

We create an actionApprove() method in CommentController as follows

public function actionApprove()

if(Yiiapp()-gtrequest-gtisPostRequest)

$comment=$this-gtloadModel()

$comment-gtapprove()

$this-gtredirect(array(rsquoindexrsquo))

else

throw new CHttpException(400rsquoInvalid requestrsquo)

In the above when the approve action is invoked via a POST request we call the approve()

method defined in the Comment model to change the status We then redirect the userbrowser to the page displaying the post that this comment belongs to

We also modify the actionIndex() method of Comment to show all comments We wouldlike to see comments pending approved to show up first

public function actionIndex()

38 4 Comment Management

$dataProvider=new CActiveDataProvider(rsquoCommentrsquo array(

rsquocriteriarsquo=gtarray(

rsquowithrsquo=gtrsquopostrsquo

rsquoorderrsquo=gtrsquotstatus tcreate time DESCrsquo

)

))

$this-gtrender(rsquoindexrsquoarray(

rsquodataProviderrsquo=gt$dataProvider

))

Notice that in the above code because both tbl post and tbl comment have columns statusand create time we need to disambiguate the corresponding column reference by prefixingthem with table alias names As described in the guide the alias for the primary table ina relational query is always t Therefore we are prefixing t to the status and create time

columns in the above code

Like the post index view the index view for CommentController uses [CListView] to displaythe comment list which in turn uses the partial view wwwrootblogprotectedviews

comment viewphp to display the detail of each individual comment We will not go intodetails here Interested readers may refer to the corresponding file in the blog demowwwrootyiidemosblogprotectedviewscomment viewphp

Chapter 5

Portlets

51 Customizing Page Layout

Features like rdquothe most recent commentsrdquo rdquotag cloudrdquo are better to be implemented inportlets A portlet is a pluggable user interface component that renders a fragment ofHTML code In this section we describe how to set up the portlet architecture for ourblog application

Before we go into details about developing portlets we first need to adjust our page layoutso that the portlets can be placed in a side bar

A page layout determines how to share common sections (eg header footer) amongdifferent pages and how to embed contents that are specific for different pages In Yiia page layout is represented as a view under the protectedviewslayouts folder Whenwe call the render() method in a controller the rendering result will be automaticallyembedded into this layout view before it is displayed to end users The default layoutview is named as main which means the wwwrootblogprotectedviewslayoutsmain

php file in our blog application

We could directly modify the main layout to add a side bar However doing so wouldmake some irrelevant pages such as about contact pages to have a side bar which is notalways desirable For this reason we need different layouts for different pages

We thus create two different layouts column1 and column2 The former displays the pagein a single column while the latter displays two columns in the page one of which is theside bar We will use column2 layout for the post and comment pages and column1 forthe rest of the pages Because both column1 and column2 still share the same header andfooter sections we let them to share the main layout

Based on the above description we create the column1 layout as follows

ltphp $this-gtbeginContent(rsquolayoutsmainrsquo) gt

ltdiv class=containergt

40 5 Portlets

ltdiv id=contentgt

ltphp echo $content gt

ltdivgtlt-- content --gt

ltdivgt

ltphp $this-gtendContent() gt

and the column2 layout as follows

ltphp $this-gtbeginContent(rsquolayoutsmainrsquo) gt

ltdiv class=containergt

ltdiv class=span-18gt

ltdiv id=contentgt

ltphp echo $content gt

ltdivgtlt-- content --gt

ltdivgt

ltdiv class=span-6 lastgt

ltdiv id=sidebargt

portlets to be placed here

ltdivgtlt-- sidebar --gt

ltdivgt

ltdivgt

ltphp $this-gtendContent() gt

As we can see both column1 and column2 are enclosed in a pair of beginContent andendContent calls They essentially use the specified view layoutsmain to decorate theenclosed content In all these layout views the variable $content refers to the dynamiccontent to be embedded into the layout

Besides customizing the layout view file we also need to adjust the CSS file wwwroot

blogcssmaincss so that the overall appearance would look like what we see in the blogdemo We will not go into details here

Info The skeleton application generated by the yiic tool uses the BluePrint CSSframework It provides a nice font reset and a robust grid system For more detailson how to use this framework please refer to its documentation

After creating the new layouts we need to set the layout property of controllers to makeuse of them So far we have three controllers SiteController PostController andCommentController We set SiteController to use column1 as the default layout whilethe rest two of the controllers use column2 for example

class PostController extends Controller

52 Creating User Menu Portlet 41

public $layout=rsquocolumn2rsquo

Tip If some view of the controller does not use the same layout as declaredby the layout property it may customize it in the view by a simple assignment$this-gtlayout=rsquonewlayoutrsquo

52 Creating User Menu Portlet

Based on the requirements analysis we need three different portlets the rdquouser menurdquoportlet the rdquotag cloudrdquo portlet and the rdquorecent commentsrdquo portlet We will develop theseportlets by extending the [CPortlet] widget provided by Yii

In this section we will develop our first concrete portlet - the user menu portlet whichdisplays a list of menu items that are only available to authenticated users The menucontains four items

bull Approve Comments a hyperlink that leads to a list of comments pending approval

bull Create New Post a hyperlink that leads to the post creation page

bull Manage Posts a hyperlink that leads to the post management page

bull Logout a link button that would log out the current user

521 Creating UserMenu Class

We create the UserMenu class to represent the logic part of the user menu portlet Theclass is saved in the file wwwrootblogprotectedcomponentsUserMenuphp which has thefollowing content

Yiiimport(rsquoziiwidgetsCPortletrsquo)

class UserMenu extends CPortlet

public function init()

$this-gttitle=CHtmlencode(Yiiapp()-gtuser-gtname)

parentinit()

42 5 Portlets

protected function renderContent()

$this-gtrender(rsquouserMenursquo)

The UserMenu class extends from the CPortlet class that we created previously It overridesboth the init() method and the renderContent() method of CPortlet The former setsthe portlet title to be the name of the current user the latter generates the portlet bodycontent by rendering a view named userMenu

Tip Notice that we have to explicitly include the CPortlet class by calling Yii

import() before we refer to it the first time This is because CPortlet is part of thezii project ndash the official extension library for Yii For performance considerationclasses in this project are not listed as core classes Therefore we have to import itbefore we use it the first time

522 Creating userMenu View

Next we create the userMenu view which is saved in the file wwwrootblogprotected

componentsviewsuserMenuphp

ltulgt

ltligtltphp echo CHtmllink(rsquoCreate New Postrsquoarray(rsquopostcreatersquo)) gtltligt

ltligtltphp echo CHtmllink(rsquoManage Postsrsquoarray(rsquopostadminrsquo)) gtltligt

ltligtltphp echo CHtmllink(rsquoApprove Commentsrsquoarray(rsquocommentindexrsquo))

rsquo (rsquo Commentmodel()-gtpendingCommentCount rsquo)rsquo gtltligt

ltligtltphp echo CHtmllink(rsquoLogoutrsquoarray(rsquositelogoutrsquo)) gtltligt

ltulgt

Info By default view files for a widget should be placed under the views sub-directory of the directory containing the widget class file The file name must bethe same as the view name

523 Using UserMenu Portlet

It is time for us to make use of our newly completed UserMenu portlet We modify thelayout view file wwwrootblogprotectedviewslayoutscolumn2php as follows

53 Creating Tag Cloud Portlet 43

ltdiv id=sidebargt

ltphp if(Yiiapp()-gtuser-gtisGuest) $this-gtwidget(rsquoUserMenursquo) gt

ltdivgt

In the above we call the widget() method to generate and execute an instance of theUserMenu class Because the portlet should only be displayed to authenticated users weonly call widget() when the isGuest property of the current user is false (meaning theuser is authenticated)

524 Testing UserMenu Portlet

Letrsquos test what we have so far

1 Open a browser window and enter the URL httpwwwexamplecomblogindex

php Verify that there is nothing displayed in the side bar section of the page

2 Click on the Login hyperlink and fill out the login form to login If successful verifythat the UserMenu portlet appears in the side bar and the portlet has the usernameas its title

3 Click on the rsquoLogoutrsquo hyperlink in the UserMenu portlet Verify that the logout actionis successful and the UserMenu portlet disappears

525 Summary

What we have created is a portlet that is highly reusable We can easily reuse it in adifferent project with little or no modification Moreover the design of this portlet followsclosely the philosophy that logic and presentation should be separated While we did notpoint this out in the previous sections such practice is used nearly everywhere in a typicalYii application

53 Creating Tag Cloud Portlet

Tag cloud displays a list of post tags with visual decorations hinting the popularity of eachindividual tag

531 Creating TagCloud Class

We create the TagCloud class in the file wwwrootblogprotectedcomponentsTagCloud

php The file has the following content

44 5 Portlets

Yiiimport(rsquoziiwidgetsCPortletrsquo)

class TagCloud extends CPortlet

public $title=rsquoTagsrsquo

public $maxTags=20

protected function renderContent()

$tags=Tagmodel()-gtfindTagWeights($this-gtmaxTags)

foreach($tags as $tag=gt$weight)

$link=CHtmllink(CHtmlencode($tag) array(rsquopostindexrsquorsquotagrsquo=gt$tag))

echo CHtmltag(rsquospanrsquo array(

rsquoclassrsquo=gtrsquotagrsquo

rsquostylersquo=gtfont-size$weightpt) $link)n

Unlike the UserMenu portlet the TagCloud portlet does not use a view Instead its presen-tation is done in the renderContent() method This is because the presentation does notcontain much HTML tags

We display each tag as a hyperlink to the post index page with the corresponding tagparameter The font size of each tag link is adjusted according to their relative weightamong other tags If a tag has higher frequency value than the other it will have a biggerfont size

532 Using TagCloud Portlet

Usage of the TagCloud portlet is very simple We modify the layout file wwwrootblog

protectedviewslayoutsmainphp as follows

ltdiv id=sidebargt

ltphp if(Yiiapp()-gtuser-gtisGuest) $this-gtwidget(rsquoUserMenursquo) gt

ltphp $this-gtwidget(rsquoTagCloudrsquo array(

rsquomaxTagsrsquo=gtYiiapp()-gtparams[rsquotagCloudCountrsquo]

)) gt

ltdivgt

54 Creating Recent Comments Portlet 45

54 Creating Recent Comments Portlet

In this section we create the last portlet that displays a list of comments recently pub-lished

541 Creating RecentComments Class

We create the RecentComments class in the file wwwrootblogprotectedcomponentsRecentCommentsphp The file has the following content

Yiiimport(rsquoziiwidgetsCPortletrsquo)

class RecentComments extends CPortlet

public $title=rsquoRecent Commentsrsquo

public $maxComments=10

public function getRecentComments()

return Commentmodel()-gtfindRecentComments($this-gtmaxComments)

protected function renderContent()

$this-gtrender(rsquorecentCommentsrsquo)

In the above we invoke the findRecentComments method which is defined in the Comment

class as follows

class Comment extends CActiveRecord

public function findRecentComments($limit=10)

return $this-gtwith(rsquopostrsquo)-gtfindAll(array(

rsquoconditionrsquo=gtrsquotstatus=rsquoselfSTATUS APPROVED

rsquoorderrsquo=gtrsquotcreate time DESCrsquo

rsquolimitrsquo=gt$limit

))

46 5 Portlets

542 Creating recentComments View

The recentComments view is saved in the file wwwrootblogprotectedcomponentsviews

recentCommentsphp It simply displays every comment returned by the RecentComments

getRecentComments() method

543 Using RecentComments Portlet

We modify the layout file wwwrootblogprotectedviewslayoutsmainphp to embedthis last portlet

ltdiv id=sidebargt

ltphp if(Yiiapp()-gtuser-gtisGuest) $this-gtwidget(rsquoUserMenursquo) gt

ltphp $this-gtwidget(rsquoTagCloudrsquo array(

rsquomaxTagsrsquo=gtYiiapp()-gtparams[rsquotagCloudCountrsquo]

)) gt

ltphp $this-gtwidget(rsquoRecentCommentsrsquo array(

rsquomaxCommentsrsquo=gtYiiapp()-gtparams[rsquorecentCommentCountrsquo]

)) gt

ltdivgt

Chapter 6

Final Work

61 Beautifying URLs

The URLs linking various pages of our blog application currently look ugly For examplethe URL for the page showing a post looks like the following

indexphpr=postshowampid=1

In this section we describe how to beautify these URLs and make them SEO-friendlyOur goal is to be able to use the following URLs in the application

1 indexphppostsyii leads to the page showing a list of posts with tag yii

2 indexphppost2A+Test+Post leads to the page showing the detail of the postwith ID 2 whose title is A Test Post

3 indexphppostupdate1 leads to the page that allows updating the post with ID1

Note that in the second URL format we include the post title in the URL This is mainlyto make the URL SEO friendly It is said that search engines may also respect the wordsfound in a URL when it is being indexed

To achieve our goal we modify the application configuration as follows

return array(

rsquocomponentsrsquo=gtarray(

rsquourlManagerrsquo=gtarray(

rsquourlFormatrsquo=gtrsquopathrsquo

rsquorulesrsquo=gtarray(

48 6 Final Work

rsquopostltidd+gtlttitlegtrsquo=gtrsquopostviewrsquorsquopostslttaggtrsquo=gtrsquopostindexrsquo

)

)

)

)

In the above we configure the urlManager component by setting its urlFormat propertyto be path and adding a set of rules

The rules are used by urlManager to parse and create the URLs in the desired formatFor example the first rule says that if a URL indexphppostsyii is requested theurlManager component should be responsible to dispatch the request to the route post

index and generate a tag GET parameter with the value yii On the other hand whencreating a URL with the route postindex and parameter tag the urlManager componentwill also use this rule to generate the desired URL indexphppostsyii For this reasonwe say that urlManager is a two-way URL manager

The urlManager component can further beautify our URLs such as hiding indexphp inthe URLs appending suffix like html to the URLs We can obtain these features easilyby configuring various properties of urlManager in the application configuration For moredetails please refer to the Guide

62 Logging Errors

A production Web application often needs sophisticated logging for various events In ourblog application we would like to log the errors occurring when it is being used Sucherrors could be programming mistakes or usersrsquo misuse of the system Logging these errorswill help us to improve the blog application

We enable the error logging by modifying the application configuration as follows

return array(

rsquopreloadrsquo=gtarray(rsquologrsquo)

rsquocomponentsrsquo=gtarray(

rsquologrsquo=gtarray(

rsquoclassrsquo=gtrsquoCLogRouterrsquo

rsquoroutesrsquo=gtarray(

array(

rsquoclassrsquo=gtrsquoCFileLogRoutersquo

rsquolevelsrsquo=gtrsquoerror warningrsquo

63 Final Tune-up and Deployment 49

)

)

)

)

)

With the above configuration if an error or warning occurs detailed information will belogged and saved in a file located under the directory wwwrootblogprotectedruntime

The log component offers more advanced features such as sending log messages to a listof email addresses displaying log messages in JavaScript console window etc For moredetails please refer to the Guide

63 Final Tune-up and Deployment

We are close to finish our blog application Before deployment we would like to do sometune-ups

631 Changing Home Page

We change to use the post list page as the home page We modify the application config-uration as follows

return array(

rsquodefaultControllerrsquo=gtrsquopostrsquo

)

Tip Because PostController already declares list to be its default action whenwe access the home page of the application we will see the result generated by thelist action of the post controller

632 Enabling Schema Caching

Because ActiveRecord relies on the metadata about tables to determine the column infor-mation it takes time to read the metadata and analyze it This may not be a problemduring development stage but for an application running in production mode it is a totalwaste of time if the database schema does not change Therefore we should enable theschema caching by modifying the application configuration as follows

50 6 Final Work

return array(

rsquocomponentsrsquo=gtarray(

rsquocachersquo=gtarray(

rsquoclassrsquo=gtrsquoCDbCachersquo

)

rsquodbrsquo=gtarray(

rsquoclassrsquo=gtrsquosystemdbCDbConnectionrsquo

rsquoconnectionStringrsquo=gtrsquosqlitewwwrootblogprotecteddatablogdbrsquo

rsquoschemaCachingDurationrsquo=gt3600

)

)

)

In the above we first add a cache component which uses a default SQLite database as thecaching storage If our server is equipped with other caching extensions such as APCwe could change to use them as well We also modify the db component by setting itsschemaCachingDuration property to be 3600 which means the parsed database schemadata can remain valid in cache for 3600 seconds

633 Disabling Debugging Mode

We modify the entry script file wwwrootblogindexphp by removing the line definingthe constant YII DEBUG This constant is useful during development stage because it allowsYii to display more debugging information when an error occurs However when theapplication is running in production mode displaying debugging information is not agood idea because it may contain sensitive information such as where the script file islocated and the content in the file etc

634 Deploying the Application

The final deployment process manly involves copying the directory wwwrootblog to thetarget directory The following checklist shows every needed step

1 Install Yii in the target place if it is not available

2 Copy the entire directory wwwrootblog to the target place

3 Edit the entry script file indexphp by pointing the $yii variable to the new Yiibootstrap file

4 Edit the file protectedyiicphp by setting the $yiic variable to be the new Yiiyiicphp file

64 Future Enhancements 51

5 Change the permission of the directories assets and protectedruntime so that theyare writable by the Web server process

64 Future Enhancements

641 Using a Theme

Without writing any code our blog application is already themeable To use a themewe mainly need to develop the theme by writing customized view files in the theme Forexample to use a theme named classic that uses a different page layout we would createa layout view file wwwrootblogthemesclassicviewslayoutsmainphp We also needto change the application configuration to indicate our choice of the classic theme

return array(

rsquothemersquo=gtrsquoclassicrsquo

)

642 Internationalization

We may also internationalize our blog application so that its pages can be displayed indifferent languages This mainly involves efforts in two aspects

First we may create view files in different languages For example for the list pageof PostController we can create a view file wwwrootblogprotectedviewspostzh cn

listphp When the application is configured to use simplified Chinese (the language codeis zh cn) Yii will automatically use this new view file instead of the original one

Second we may create message translations for those messages generated by code Themessage translations should be saved as files under the directory wwwrootblogprotected

messages We also need to modify the code where we use text strings by enclosing themin the method call Yiit()

For more details about internationalization please refer to the Guide

643 Improving Performance with Cache

While the Yii framework itself is very efficient it is not necessarily true that an applicationwritten in Yii efficient There are several places in our blog application that we can improvethe performance For example the tag clould portlet could be one of the performancebottlenecks because it involves complex database query and PHP logic

52 6 Final Work

We can make use of the sophisticated caching feature provided by Yii to improve theperformance One of the most useful components in Yii is COutputCache which caches afragment of page display so that the underlying code generating the fragment does not needto be executed for every request For example in the layout file wwwrootblogprotected

viewslayoutsmainphp we can enclose the tag cloud portlet with COutputCache

ltphp if($this-gtbeginCache(rsquotagCloudrsquo array(rsquodurationrsquo=gt3600))) gt

ltphp $this-gtwidget(rsquoTagCloudrsquo array(

rsquomaxTagsrsquo=gtYiiapp()-gtparams[rsquotagCloudCountrsquo]

)) gt

ltphp $this-gtendCache() gt

With the above code the tag cloud display will be served from cache instead of beinggenerated on-the-fly for every request The cached content will remain valid in cache for3600 seconds

644 Adding New Features

Our blog application only has very basic functionalities To become a complete blogsystem more features are needed for example calendar portlet email notifications postcategorization archived post portlet and so on We will leave the implementation of thesefeatures to interested readers

  • Contents
  • License
  • Getting Started
    • Building a Blog System using Yii
    • Testdriving with Yii
      • Installing Yii
      • Creating Skeleton Application
      • Application Workflow
        • Requirements Analysis
        • Overall Design
          • Initial Prototyping
            • Setting Up Database
              • Creating Database
              • Establishing Database Connection
                • Scaffolding
                • Authenticating User
                • Summary
                  • Post Management
                    • Customizing Post Model
                      • Customizing rules() Method
                      • Customizing relations() Method
                      • Representing Status in Text
                        • Creating and Updating Posts
                          • Customizing Access Control
                          • Customizing create and update Operations
                            • Displaying Posts
                              • Customizing view Operation
                              • Customizing index Operation
                                • Managing Posts
                                  • Listing Posts in Tabular View
                                  • Deleting Posts
                                      • Comment Management
                                        • Customizing Comment Model
                                          • Customizing rules() Method
                                          • Customizing attributeLabels() Method
                                          • Customizing Saving Process
                                            • Creating and Displaying Comments
                                              • Displaying Comments
                                              • Creating Comments
                                                • Managing Comments
                                                  • Updating and Deleting Comments
                                                  • Approving Comments
                                                      • Portlets
                                                        • Customizing Page Layout
                                                        • Creating User Menu Portlet
                                                          • Creating UserMenu Class
                                                          • Creating userMenu View
                                                          • Using UserMenu Portlet
                                                          • Testing UserMenu Portlet
                                                          • Summary
                                                            • Creating Tag Cloud Portlet
                                                              • Creating TagCloud Class
                                                              • Using TagCloud Portlet
                                                                • Creating Recent Comments Portlet
                                                                  • Creating RecentComments Class
                                                                  • Creating recentComments View
                                                                  • Using RecentComments Portlet
                                                                      • Final Work
                                                                        • Beautifying URLs
                                                                        • Logging Errors
                                                                        • Final Tune-up and Deployment
                                                                          • Changing Home Page
                                                                          • Enabling Schema Caching
                                                                          • Disabling Debugging Mode
                                                                          • Deploying the Application
                                                                            • Future Enhancements
                                                                              • Using a Theme
                                                                              • Internationalization
                                                                              • Improving Performance with Cache
                                                                              • Adding New Features
Page 5: Yii Blog 1.1.0 Demo Tutor

iv Contents

641 Using a Theme 51

642 Internationalization 51

643 Improving Performance with Cache 51

644 Adding New Features 52

License of Yii

The Yii framework is free software It is released under the terms of the following BSDLicense

Copyright ccopy2008-2009 by Yii Software LLC All rights reserved

Redistribution and use in source and binary forms with or without modification arepermitted provided that the following conditions are met

1 Redistributions of source code must retain the above copyright notice this list ofconditions and the following disclaimer

2 Redistributions in binary form must reproduce the above copyright notice this list ofconditions and the following disclaimer in the documentation andor other materialsprovided with the distribution

3 Neither the name of Yii Software LLC nor the names of its contributors may beused to endorse or promote products derived from this software without specificprior written permission

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS rdquoAS

ISrdquo AND ANY EXPRESS OR IMPLIED WARRANTIES INCLUDING BUT NOT LIMITED TO THE

IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PUR-

POSE ARE DISCLAIMED IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBU-

TORS BE LIABLE FOR ANY DIRECT INDIRECT INCIDENTAL SPECIAL EXEMPLARY OR

CONSEQUENTIAL DAMAGES (INCLUDING BUT NOT LIMITED TO PROCUREMENT OF SUB-

STITUTE GOODS OR SERVICES LOSS OF USE DATA OR PROFITS OR BUSINESS INTERRUP-

TION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY WHETHER IN CONTRACT

STRICT LIABILITY OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY

WAY OUT OF THE USE OF THIS SOFTWARE EVEN IF ADVISED OF THE POSSIBILITY OF

SUCH DAMAGE

vi Contents

Chapter 1

Getting Started

11 Building a Blog System using Yii

This tutorial describes how to use Yii to develop a blog application shown as the blog demowhich can be found in the Yii release files It explains in detail every step to be takenduring the development which may also be applied in developing other Web applicationsAs a complement to the Guide and the Class Reference of Yii this tutorial aims to showpractical usage of Yii instead of thorough and definitive description

Readers of this tutorial are not required to have prior knowledge about Yii However basicknowledge of object-oriented programming (OOP) and database programming would helpreaders to understand the tutorial more easily

This tutorial is released under the Terms of Yii Documentation

12 Testdriving with Yii

In this section we describe how to create a skeleton application that will serve as ourstarting point For simplicity we assume that the document root of our Web server iswwwroot and the corresponding URL is httpwwwexamplecom

121 Installing Yii

We first install the Yii framework Grab a copy of the Yii release file (version 110 orabove) from wwwyiiframeworkcom and unpack it to the directory wwwrootyii Doublecheck to make sure that there is a directory wwwrootyiiframework

Tip The Yii framework can be installed anywhere in the file system not necessarilyunder a Web folder Its framework directory contains all framework code and isthe only framework directory needed when deploying an Yii application A singleinstallation of Yii can be used by multiple Yii applications

2 1 Getting Started

After installing Yii open a browser window and access the URL httpwwwexample

comyiirequirementsindexphp It shows the requirement checker provided in the Yiirelease For our blog application besides the minimal requirements needed by Yii we alsoneed to enable both the pdo and pdo sqlite PHP extensions so that we can access SQLitedatabases

122 Creating Skeleton Application

We then use the yiic tool to create a skeleton application under the directory wwwroot

blog The yiic tool is a command line tool provided in the Yii release It can be used togenerate code to reduce certain repetitive coding tasks

Open a command window and execute the following command

wwwrootyiiframeworkyiic webapp wwwrootblog

Create a Web application under rsquowwwrootblogrsquo [Yes|No]y

Tip In order to use the yiic tool as shown above the CLI PHP program must beon the command search path If not the following command may be used instead

pathtophp wwwrootyiiframeworkyiicphp webapp wwwrootblog

To try out the application we just created open a Web browser and navigate to the URLhttpwwwexamplecomblogindexphp We should see that our skeleton applicationalready has four fully functional pages the homepage the about page the contact pageand the login page

In the following we briefly describe what we have in this skeleton application

Entry Script

We have an entry script file wwwrootblogindexphp which has the following content

ltphp

$yii=rsquowwwrootframeworkyiiphprsquo

$config=dirname( FILE )rsquoprotectedconfigmainphprsquo

remove the following line when in production mode

defined(rsquoYII DEBUGrsquo) or define(rsquoYII DEBUGrsquotrue)

12 Testdriving with Yii 3

require once($yii)

YiicreateWebApplication($config)-gtrun()

This is the only script that Web users can directly access The script first includes theYii bootstrap file yiiphp It then creates an application instance with the specifiedconfiguration and executes the application

Base Application Directory

We also have an application base directory wwwrootblogprotected The majority of ourcode and data will be placed under this directory and it should be protected from beingaccessed by Web users For Apache httpd Web server we place under this directory ahtaccess file with the following content

deny from all

For other Web servers please refer to the corresponding manual on how to protect adirectory from being accessed by Web users

123 Application Workflow

To help understand how Yii works we describe the main workflow in our skeleton appli-cation when a user is accessing its contact page

1 The user requests the URL httpwwwexamplecomblogindexphpr=sitecontact

2 The entry script is executed by the Web server to process the request

3 An application instance is created and configured with initial property values speci-fied in the application configuration file wwwrootblogprotectedconfigmainphp

4 The application resolves the request into a controller and a controller action For thecontact page request it is resolved as the site controller and the contact action (theactionContact method in wwwrootblogprotectedcontrollersSiteControllerphp)

5 The application creates the site controller in terms of a SiteController instanceand then executes it

6 The SiteController instance executes the contact action by calling its actionContact()method

4 1 Getting Started

7 The actionContact method renders a view named contact to the Web user Inter-nally this is achieved by including the view file wwwrootblogprotectedviews

sitecontactphp and embedding the result into the layout file wwwrootblog

protectedviewslayoutsmainphp

13 Requirements Analysis

The blog system that we are going to develop is a single user system The owner of thesystem will be able to perform the following actions

bull Login and logout

bull Create update and delete posts

bull Publish unpublish and archive posts

bull Approve and delete comments

All other users are guest users who can perform the following actions

bull Read posts

bull Create comments

Additional Requirements for this system include

bull The homepage of the system should display a list of the most recent posts

bull If a page contains more than 10 posts they should be displayed in pages

bull The system should display a post together with its comments

bull The system should be able to list posts with a specified tag

bull The system should show a cloud of tags indicating their use frequencies

bull The system should show a list of most recent comments

bull The system should be themeable

bull The system should use SEO-friendly URLs

14 Overall Design 5

14 Overall Design

Based on the analysis of the requirements we decide to use the following database tablesto store the persistent data for our blog application

bull tbl user stores the user information including username and password

bull tbl post stores the blog post information It mainly consists of the following columns

ndash title required title of the post

ndash content required body content of the post which uses the Markdown format

ndash status required status of the post which can be one of following values

lowast 1 meaning the post is in draft and is not visible to public

lowast 2 meaning the post is published to public

lowast 3 meaning the post is outdated and is not visible in the post list (stillaccessible individually though)

ndash tags optional a list of comma-separated words categorizing the post

bull tbl comment stores the post comment information Each comment is associated witha post and mainly consists of the following columns

ndash name required the author name

ndash email required the author email

ndash website optional the author website URL

ndash content required the comment content in plain text format

ndash status required status of the comment which indicates whether the commentis approved (value 2) or not (value 1)

bull tbl tag stores post tag frequency information that is needed to implement the tagcloud feature The table mainly contains the following columns

ndash name required the unique tag name

ndash frequency required the number of times that the tag appears in posts

bull tbl lookup stores generic lookup information It is essentially a map between integervalues and text strings The former is the data representation in our code while thelatter is the corresponding presentation to end users For example we use integer1 to represent the draft post status and string Draft to display this status to endusers This table mainly contains the following columns

6 1 Getting Started

ndash name the textual representation of the data item that is to be displayed to endusers

ndash code the integer representation of the data item

ndash type the type of the data item

ndash position the relative display order of the data item among other items of thesame type

The following entity-relation (ER) diagram shows the table structure and relationshipsabout the above tables

Figure 11 Entity-Relation Diagram of the Blog Database

Complete SQL statements corresponding to the above ER diagram may be found inthe blog demo In our Yii installation they are in the file wwwrootyiidemosblog

protecteddataschemasqlitesql

Info We name all our table names and column names in lower case This is becausedifferent DBMS often have different case-sensitivity treatment and we want to avoidtroubles like this

We also prefix all our tables with tbl This serves for two purposes First the prefixintroduces a namespace to these tables in case when they need to coexist with othertables in the same database which often happens in a shared hosting environmentwhere a single database is being used by multiple applications Second using tableprefix reduces the possibility of having some table names that are reserved keywordsin DBMS

14 Overall Design 7

We divide the development of our blog application into the following milestones

bull Milestone 1 creating a prototype of the blog system It should consist of most ofthe required functionalities

bull Milestone 2 completing post management It includes creating listing showingupdating and deleting posts

bull Milestone 3 completing comment management It includes creating listing ap-proving updating and deleting post comments

bull Milestone 4 implementing portlets It includes user menu login tag cloud andrecent comments portlets

bull Milestone 5 final tune-up and deployment

8 1 Getting Started

Chapter 2

Initial Prototyping

21 Setting Up Database

Having created a skeleton application and finished the database design in this section wewill create the blog database and establish the connection to it in the skeleton application

211 Creating Database

We choose to create a SQLite database Because the database support in Yii is built on topof PDO we can easily switch to use a different type of DBMS (eg MySQL PostgreSQL)without the need to change our application code

We create the database file blogdb under the directory wwwrootblogprotecteddataNote that both the directory and the database file have to be writable by the Web serverprocess as required by SQLite We may simply copy the database file from the blogdemo in our Yii installation which is located at wwwrootyiidemosblogprotecteddatablogdb We may also generate the database by executing the SQL statements in the filewwwrootyiidemosblogprotecteddataschemasqlitesql

Tip To execute SQL statements we may use the sqlite3 command line tool thatcan be found in the SQLite official website

212 Establishing Database Connection

To use the blog database in the skeleton application we created we need to modify itsapplication configuration which is stored in the PHP script wwwrootblogprotected

configmainphp The script returns an associative array consisting of name-value pairseach of which is used to initialize a writable property of the application instance

We configure the db component as follows

10 2 Initial Prototyping

return array(

rsquocomponentsrsquo=gtarray(

rsquodbrsquo=gtarray(

rsquoconnectionStringrsquo=gtrsquosqlitewwwrootblogprotecteddatablogdbrsquo

rsquotablePrefixrsquo=gtrsquotbl rsquo

)

)

)

The above configuration says that we have a db application component whose connectionStringproperty should be initialized as sqlitewwwrootblogprotecteddatablogdb and whosetablePrefix property should be tbl

With this configuration we can access the DB connection object using Yiiapp()-gtdb

at any place in our code Note that Yiiapp() returns the application instance that wecreate in the entry script If you are interested in possible methods and properties that theDB connection has you may refer to its class reference However in most cases we are notgoing to use this DB connection directly Instead we will use the so-called ActiveRecordto access the database

We would like to explain a bit more about the tablePrefix property that we set in theconfiguration This tells the db connection that it should respect the fact we are usingtbl as the prefix to our database table names In particular if in a SQL statement thereis a token enclosed within double curly brackets (eg post then the db connectionshould translate it into a name with the table prefix (eg tbl post) before sending it toDBMS for execution This feature is especially useful if in future we need to modify thetable name prefix without touching our source code For example if we are developing ageneric content management system (CMS) we may exploit this feature so that when itis being installed in a new environment we can allow users to choose a table prefix theylike

22 Scaffolding 11

Tip If you want to use MySQL instead of SQLite to store data you may cre-ate a MySQL database named blog using the SQL statements in wwwrootyii

demosblogprotecteddataschemamysqlsql Then modify the applicationconfiguration as follows

return array(

rsquocomponentsrsquo=gtarray(

rsquodbrsquo=gtarray(

rsquoconnectionStringrsquo =gt rsquomysqlhost=localhostdbname=blogrsquo

rsquoemulatePreparersquo =gt true

rsquousernamersquo =gt rsquorootrsquo

rsquopasswordrsquo =gt rsquorsquo

rsquocharsetrsquo =gt rsquoutf8rsquo

rsquotablePrefixrsquo =gt rsquotbl rsquo

)

)

)

22 Scaffolding

Create read update and delete (CRUD) are the four basic operations of data objects in anapplication Because the task of implementing the CRUD operations is so common whendeveloping Web applications Yii provides a tool to automate this process (also known asscaffolding) In this section we will describe how to use the tool to implement CRUD forposts and comments

Open a command window and run the following commands

wwwrootyiiframeworkyiic shell wwwrootblogindexphp

Yii Interactive Tool v11

Please type rsquohelprsquo for help Type rsquoexitrsquo to quit

gtgt model

gtgt crud Post

gtgt crud Comment

gtgt exit

12 2 Initial Prototyping

Info Some PHP installations may use a different phpini file for command line(CLI) PHP parser As a result when running the above yiic commands youmay encounter errors like rdquoYiiBaseinclude(PDOphp) failed to open streamrdquoor rdquocould not find driverrdquo Please double check your CLI PHP configuration byexecuting the following command

php -r phpinfo()

The result of the above command will show which phpini file is being used andwhich extensions are loaded If a wrong phpini file is used you may use thefollowing command to explicitly specify the correct phpini to use

php -c phpini wwwrootyiiframeworkyiicphp shell wwwrootblogindexphp

The commands above accomplish two tasks First the model command generates a modelclass file for each table in the blog database Second the crud commands generate thecode needed by the CRUD operations for the Post and Comment models

Tip The command model generates a model class for every table in the databaseSometimes we probably donrsquot want to do so (eg the database contains some irrel-evant tables) In this case we can create model classes one by one For exampleto create the User model we can use the command model User The model com-mand also has some more advanced usages For more details execute help model

command

We can test the generated code by accessing the following URLs

httpwwwexamplecomblogindexphpr=post

httpwwwexamplecomblogindexphpr=comment

Notice that the post and comment features implemented by the generated code are com-pletely independent of each other Also when creating a new post or comment we arerequired to enter information such as author id and create time which in real applica-tion should be set by the program Donrsquot worry We will fix these problems in the nextmilestones For now we should be fairly satisfied as this prototype already contains mostfeatures that we need to implement for the blog application

To prepare for the next milestones letrsquos take a closer look at the files generated by theabove commands All the files are generated under wwwrootblogprotected For conve-nience we group them into model files controller files and view files

22 Scaffolding 13

bull model files

ndash modelsUserphp contains the User class that extends from CActiveRecord andcan be used to access the tbl user database table

ndash modelsPostphp contains the Post class that extends from CActiveRecord andcan be used to access the tbl post database table

ndash modelsTagphp contains the Tag class that extends from CActiveRecord andcan be used to access the tbl tag database table

ndash modelsCommentphp contains the Comment class that extends from CActiveRe-cord and can be used to access the tbl comment database table

ndash modelsLookupphp contains the Lookup class that extends from CActiveRecordand can be used to access the tbl lookup database table

bull controller file

ndash controllersPostControllerphp contains the PostController class which is thecontroller in charge of all CRUD operations about posts

ndash controllersCommentControllerphp contains the CommentController class whichis the controller in charge of all CRUD operations about comments

bull view files

ndash viewspostcreatephp is the view file that shows an HTML form to create anew post

ndash viewspostupdatephp is the view file that shows an HTML form to update anexisting post

ndash viewspostviewphp is the view file that displays the detailed information of apost

ndash viewspostindexphp is the view file that displays a list of posts

ndash viewspostadminphp is the view file that displays posts in a table with ad-ministrative commands

ndash viewspost formphp is the partial view file embedded in viewspostcreate

php and viewspostupdatephp It displays the HTML form for collecting postinformation

ndash viewspost viewphp is the partial view file used by viewspostindexphp Itdisplays the brief view of a single post

ndash a similar set of view files are also generated for comment

14 2 Initial Prototyping

In order to understand better how the above files are used we show in the following theworkflow that occurs in the blog application when displaying a list of posts

1 The user requests the URL httpwwwexamplecomblogindexphpr=posts

2 The entry script is executed by the Web server which creates and initializes anapplication instance to handle the request

3 The application creates an instance of PostController and executes it

4 The PostController instance executes the index action by calling its actionIndex()

method Note that index is the default action if the user does not specify an actionto execute in the URL

5 The actionIndex() method queries database to bring back the list of recent posts

6 The actionIndex() method renders the index view with the post data

23 Authenticating User

Our blog application needs to differentiate between the system owner and guest usersTherefore we need to implement the user authentication feature

As you may have found that the skeleton application already provides user authenticationby checking if the username and password are both demo or admin In this section wewill modify the corresponding code so that the authentication is done against the User

database table

User authentication is performed in a class implementing the IUserIdentity interface Theskeleton application uses the UserIdentity class for this purpose The class is stored inthe file wwwrootblogprotectedcomponentsUserIdentityphp

Tip By convention the name of a class file must be the same as the correspondingclass name suffixed with the extension php Following this convention one canrefer to a class using a path alias For example we can refer to the UserIdentity

class with the alias applicationcomponentsUserIdentity Many APIs in Yiican recognize path aliases (eg YiicreateComponent()) and using path aliasesavoids the necessity of embedding absolute file paths in the code The existence ofthe latter often causes trouble when we deploy an application

We modify the UserIdentity class as follows

23 Authenticating User 15

ltphp

class UserIdentity extends CUserIdentity

private $ id

public function authenticate()

$username=strtolower($this-gtusername)

$user=Usermodel()-gtfind(rsquoLOWER(username)=rsquoarray($username))

if($user===null)

$this-gterrorCode=selfERROR USERNAME INVALID

else if($user-gtvalidatePassword($this-gtpassword))

$this-gterrorCode=selfERROR PASSWORD INVALID

else

$this-gt id=$user-gtid

$this-gtusername=$user-gtusername

$this-gterrorCode=selfERROR NONE

return $this-gterrorCode==selfERROR NONE

public function getId()

return $this-gt id

In the authenticate() method we use the User class to look for a row in the User tablewhose username column is the same as the given username in a case-insensitive mannerRemember that the User class was created using the yiic tool in the prior section Becausethe User class extends from CActiveRecord we can exploit the ActiveRecord feature toaccess the User table in an OOP fashion

In order to check if the user has entered a valid password we invoke the validatePassword

method of the User class We need to modify the file wwwrootblogprotectedmodels

Userphp as follows Note that instead of storing the plain password in the database westore the hash result of the password and a randomly generated salt key When validatingthe user-entered password we should compare the hash results instead

class User extends CActiveRecord

public function validatePassword($password)

return $this-gthashPassword($password$this-gtsalt)===$this-gtpassword

16 2 Initial Prototyping

public function hashPassword($password$salt)

return md5($salt$password)

In the UserIdentity class we also override the getId() method which returns the id valueof the user found in the User table The parent implementation would return the usernameinstead Both the username and id properties will be stored in the user session and maybe accessed via Yiiapp()-gtuser from anywhere in our code

Tip In the UserIdentity class we reference the class CUserIdentity without ex-plicitly including the corresponding class file This is because CUserIdentity is acore class provided by the Yii framework Yii will automatically include the classfile for any core class when it is referenced for the first time

We also do the same with the User class This is because the User class file is placedunder the directory wwwrootblogprotectedmodels which has been added tothe PHP include path according to the following lines found in the applicationconfiguration

return array(

rsquoimportrsquo=gtarray(

rsquoapplicationmodelsrsquo

rsquoapplicationcomponentsrsquo

)

)

The above configuration says that any class whose class file is located under either wwwrootblogprotectedmodels or wwwrootblogprotectedcomponents willbe automatically included when the class is referenced for the first time

The UserIdentity class is mainly used by the LoginForm class to authenticate a user basedon the username and password input collected from the login page The following codefragment shows how UserIdentity is used

$identity=new UserIdentity($username$password)

$identity-gtauthenticate()

switch($identity-gterrorCode)

24 Summary 17

case UserIdentityERROR NONE

Yiiapp()-gtuser-gtlogin($identity)

break

Info People often get confused about identity and the user application componentThe former represents a way of performing authentication while the latter is usedto represent the information related with the current user An application can onlyhave one user component but it can have one or several identity classes dependingon what kind of authentication it supports Once authenticated an identity instancemay pass its state information to the user component so that they are globallyaccessible via user

To test the modified UserIdentity class we can browse the URL httpwwwexample

comblogindexphp and try logging in with the username and password that we store inthe User table If we use the database provided by the blog demo we should be able tologin with username demo and password demo Note that this blog system does not providethe user management feature As a result a user cannot change his account or create anew one through the Web interface The user management feature may be considered asa future enhancement to the blog application

24 Summary

We have completed the milestone 1 Letrsquos summarize what we have done so far

1 We identified the requirements to be fulfilled

2 We installed the Yii framework

3 We created a skeleton application

4 We designed and created the blog database

5 We modified the application configuration by adding the database connection

6 We generated the code that implements the basic CRUD operations for both postsand comments

7 We modified the authentication method to check against the tbl user table

18 2 Initial Prototyping

For a new project most of the time will be spent in step 1 and 4 for this first milestone

Although the code generated by the yiic tool implements fully functional CRUD oper-ations for a database table it often needs to be modified in practical applications Forthis reason in the next two milestone our job is to customize the generated CRUD codeabout posts and comments so that it reaches our initial requirements

In general we first modify the model class file by adding appropriate validation rules anddeclaring relational objects We then modify the controller action and view code for eachindividual CRUD operation

Chapter 3

Post Management

31 Customizing Post Model

The Post model class generated by the yiic tool mainly needs to be modified in two places

bull the rules() method specifies the validation rules for the model attributes

bull the relations() method specifies the related objects

Info A model consists of a list of attributes each associated with a column in thecorresponding database table Attributes can be declared explicitly as class membervariables or implicitly without any declaration

311 Customizing rules() Method

We first specify the validation rules which ensure the attribute values entered by usersare correct before they are saved to the database For example the status attribute ofPost should be an integer 1 2 or 3 The yiic tool also generates validation rules for eachmodel However these rules are based on the table column information and may not beappropriate

Based on the requirement analysis we modify the rules() method as follows

public function rules()

return array(

array(rsquotitle content statusrsquo rsquorequiredrsquo)

array(rsquotitlersquo rsquolengthrsquo rsquomaxrsquo=gt128)

array(rsquostatusrsquo rsquoinrsquo rsquorangersquo=gtarray(123))

array(rsquotagsrsquo rsquomatchrsquo rsquopatternrsquo=gtrsquo^[ws]+$rsquorsquomessagersquo=gtrsquoTags can only contain word charactersrsquo)

20 3 Post Management

array(rsquotagsrsquo rsquonormalizeTagsrsquo)

)

In the above we specify that the title content and status attributes are required thelength of title should not exceed 128 the status attribute value should be 1 (draft) 2(published) or 3 (archived) and the tags attribute should only contain word charactersand commas In addition we use normalizeTags to normalize the user-entered tags so thatthe tags are unique and properly separated with commas

The validators such as required length in and match are all built-in validators providedby Yii The normalizeTags validator is a method-based validator that we need to definein the Post class For more information about how to specify validation rules please referto the Guide

public function normalizeTags($attribute$params)

$this-gttags=Tagarray2string(array unique(Tagstring2array($this-gttags)))

The rules declared in this method are executed one by one when we call the validate() orsave() method of the model instance

Note It is very important to remember that attributes appearing in rules() mustbe those to be entered by end users Other attributes such as id and create time

in the Post model which are set by our code or database should not be in rules()For more details please refer to Securing Attribute Assignments

After making these changes we can visit the post creation page again to verify that thenew validation rules are taking effect

312 Customizing relations() Method

Lastly we customize the relations() method to specify the related objects of a post Bydeclaring these related objects in relations() we can exploit the powerful RelationalActiveRecord (RAR) feature to access the related object information of a post such asits author and comments without the need to write complex SQL JOIN statements

We customize the relations() method as follows

31 Customizing Post Model 21

public function relations()

return array(

rsquoauthorrsquo =gt array(selfBELONGS TO rsquoUserrsquo rsquoauthor idrsquo)

rsquocommentsrsquo =gt array(selfHAS MANY rsquoCommentrsquo rsquopost idrsquo

rsquoconditionrsquo=gtrsquocommentsstatus=rsquoCommentSTATUS APPROVED

rsquoorderrsquo=gtrsquocommentscreate time DESCrsquo)

rsquocommentCountrsquo =gt array(selfSTAT rsquoCommentrsquo rsquopost idrsquo

rsquoconditionrsquo=gtrsquostatus=rsquoCommentSTATUS APPROVED)

)

The above relations state that

bull A post belongs to an author whose class is User and the relationship is establishedbased on the author id attribute value of the post

bull A post has many comments whose class is Comment and the relationship is establishedbased on the post id attribute value of the comments These comments should besorted according to their creation time and the comments must be approved

bull The commentCount relation is a bit special as it returns back an aggregation resultwhich is about how many comments the post has

With the above relation declaration we can easily access the author and comments of apost like the following

$author=$post-gtauthor

echo $author-gtusername

$comments=$post-gtcomments

foreach($comments as $comment)

echo $comment-gtcontent

For more details about how to declare and use relations please refer to the Guide

313 Representing Status in Text

Because the status of a post is stored as an integer in the database we need to provide atextual representation so that it is more intuitive when being displayed to end users In alarge system the similar requirement is very common

22 3 Post Management

As a generic solution we use the tbl lookup table to store the mapping between integervalues and textual representations that are needed by other data objects We modify theLookup model class as follows to more easily access the textual data in the table

class Lookup extends CActiveRecord

private static $ items=array()

public static function items($type)

if(isset(self$ items[$type]))

selfloadItems($type)

return self$ items[$type]

public static function item($type$code)

if(isset(self$ items[$type]))

selfloadItems($type)

return isset(self$ items[$type][$code]) self$ items[$type][$code] false

private static function loadItems($type)

self$ items[$type]=array()

$models=selfmodel()-gtfindAll(array(

rsquoconditionrsquo=gtrsquotype=typersquo

rsquoparamsrsquo=gtarray(rsquotypersquo=gt$type)

rsquoorderrsquo=gtrsquopositionrsquo

))

foreach($models as $model)

self$ items[$type][$model-gtcode]=$model-gtname

Our new code mainly provides two static methods Lookupitems() and Lookupitem()The former returns a list of strings belonging to the specified data type while the latterreturns a particular string for the given data type and data value

Our blog database is pre-populated with two lookup types PostStatus and CommentStatusThe former refers to the possible post statuses while the latter the comment statuses

In order to make our code easier to read we also declare a set of constants to representthe status integer values We should use these constants through our code when referringto the corresponding status values

32 Creating and Updating Posts 23

class Post extends CActiveRecord

const STATUS DRAFT=0

const STATUS PUBLISHED=1

const STATUS ARCHIVED=2

Therefore we can call Lookupitems(rsquoPostStatusrsquo) to get the list of possible post statuses(text strings indexed by the corresponding integer values) and call Lookupitem(rsquoPostStatusrsquoPostSTATUS PUBLISHED) to get the string representation of the published status

32 Creating and Updating Posts

With the Post model ready we need to fine-tune the actions and views for the controllerPostController In this section we first customize the access control of CRUD operationswe then modify the code implementing the create and update operations

321 Customizing Access Control

The first thing we want to do is to customize the access control because the code generatedby yiic does not fit our needs

We modify the accessRules() method in the file wwwrootblogprotectedcontrollers

PostControllerphp as follows

public function accessRules()

return array(

array(rsquoallowrsquo allow all users to perform rsquolistrsquo and rsquoshowrsquo actions

rsquoactionsrsquo=gtarray(rsquoindexrsquo rsquoviewrsquo)

rsquousersrsquo=gtarray(rsquorsquo)

)

array(rsquoallowrsquo allow authenticated users to perform any action

rsquousersrsquo=gtarray(rsquorsquo)

)

array(rsquodenyrsquo deny all users

rsquousersrsquo=gtarray(rsquorsquo)

)

)

The above rules state that all users can access the index and view actions and authenti-cated users can access any actions including the admin action The user should be denied

24 3 Post Management

access in any other scenario Note that these rules are evaluated in the order they arelisted here The first rule matching the current context makes the access decision Forexample if the current user is the system owner who tries to visit the post creation pagethe second rule will match and it will give the access to the user

322 Customizing create and update Operations

The create and update operations are very similar They both need to display an HTMLform to collect user inputs validate them and save them into database The main differ-ence is that the update operation will pre-populate the form with the existing post datafound in the database For this reason the yiic tool generates a partial view wwwroot

blogprotectedviewspost formphp that is embedded in both the create and update

views to render the needed HTML form

We first change the formphp file so that the HTML form only collects the inputs wewant title content tags and status We use plain text fields to collect inputs for thefirst three attributes and a dropdown list to collect input for status The dropdown listoptions are the text displays of the possible post statuses

ltphp echo CHtmlactiveDropDownList($postrsquostatusrsquoLookupitems(rsquoPostStatusrsquo)) gt

In the above we call Lookupitems(rsquoPostStatusrsquo) to bring back the list of post statuses

We then modify the Post class so that it can automatically set some attributes (eg create

time author id) before a post is saved to the database We override the beforeSave()

method as follows

protected function beforeSave()

if(parentbeforeSave())

if($this-gtisNewRecord)

$this-gtcreate time=$this-gtupdate time=time()

$this-gtauthor id=Yiiapp()-gtuser-gtid

else

$this-gtupdate time=time()

return true

else

return false

33 Displaying Posts 25

When we save a post we want to update the tbl tag table to reflect the change of tagfrequencies We can do this work in the afterSave() method which is automaticallyinvoked by Yii after a post is successfully saved into the database

protected function afterSave()

parentafterSave()

Tagmodel()-gtupdateFrequency($this-gt oldTags $this-gttags)

private $ oldTags

protected function afterFind()

parentafterFind()

$this-gt oldTags=$this-gttags

In the implementation because we want to detect if the user changes the tags in case he isupdating an existing post we need to know what the old tags are For this reason we alsowrite the afterFind() method to keep the old tags in the variable oldTags The methodafterFind() is invoked automatically by Yii when an AR record is populated with thedata from database

We are not going to give details of the TagupdateFrequency() method here Interestedreaders may refer to the file wwwrootyiidemosblogprotectedmodelsTagphp

33 Displaying Posts

In our blog application a post may be displayed among a list of posts or by itself Theformer is implemented as the index operation while the latter the view operation In thissection we customize both operations to fulfill our initial requirements

331 Customizing view Operation

The view operation is implemented by the actionView() method in PostController Itsdisplay is generated by the view view with the view file wwwrootblogprotectedviews

postviewphp

Below is the relevant code implementing the view operation in PostController

public function actionView()

26 3 Post Management

$this-gtrender(rsquoviewrsquoarray(

$post=$this-gtloadModel()

$this-gtrender(rsquoviewrsquoarray(

rsquomodelrsquo=gt$post

))

))

private $ model

public function loadModel()

if($this-gt model===null)

if(isset($ GET[rsquoidrsquo]))

if(Yiiapp()-gtuser-gtisGuest)

$condition=rsquostatus=rsquoPostSTATUS PUBLISHED

rsquo OR status=rsquoPOSTSTATUS ARCHIVED

else

$condition=rsquorsquo

$this-gt model=Postmodel()-gtfindbyPk($ GET[rsquoidrsquo] $condition)

if($this-gt model===null)

throw new CHttpException(404rsquoThe requested page does not existrsquo)

return $this-gt model

Our change mainly lies in the loadModel() method In this method we query the Post

table according to the id GET parameter If the post is not found or if it is not publishedor archived (when the user is a guest) we will throw a 404 HTTP error Otherwise thepost object is returned to actionView() which in turn passes the post object to the viewscript for further display

Tip Yii captures HTTP exceptions (instances of CHttpException) and displaysthem in either predefined templates or customized error views The skeleton appli-cation generated by yiic already contains a customized error view in wwwroot

blogprotectedviewssiteerrorphp We can modify this file if we want tofurther customize the error display

The change in the view script is mainly about ajdusting the formatting and styles of thepost display We will not go into details here Interested readers may refer to wwwroot

blogprotectedviewspostviewphp

33 Displaying Posts 27

332 Customizing index Operation

Like the view operation we customize the index operation in two places the actionIndex()

method in PostController and the view file wwwrootblogprotectedviewspostindex

php We mainly need to add the support for displaying a list of posts that are associatedwith a specified tag

Below is the modified actionIndex() method in PostController

public function actionIndex()

$criteria=new CDbCriteria(array(

rsquoconditionrsquo=gtrsquostatus=rsquoPostSTATUS PUBLISHED

rsquoorderrsquo=gtrsquoupdate time DESCrsquo

rsquowithrsquo=gtrsquocommentCountrsquo

))

if(isset($ GET[rsquotagrsquo]))

$criteria-gtaddSearchCondition(rsquotagsrsquo$ GET[rsquotagrsquo])

$dataProvider=new CActiveDataProvider(rsquoPostrsquo array(

rsquopaginationrsquo=gtarray(

rsquopageSizersquo=gtYiiapp()-gtparams[rsquopostsPerPagersquo]

)

rsquocriteriarsquo=gt$criteria

))

$this-gtrender(rsquoindexrsquoarray(

rsquodataProviderrsquo=gt$dataProvider

))

In the above we first create a query criteria for retrieving post list The criteria statesthat only published posts should be returned and they should be sorted according to theirupdate time in descending order Because when displaying a post in the list we want toshow how many comments the post has received in the criteria we also specify to bringback commentCount which if you remember is a relation declared in Postrelations()

In case when a user wants to see posts with a specific tag we would add a search conditionto the criteria to look for the specified tag

Using the query criteria we create a data provider which mainly serves for three purposesFirst it does pagination of the data when too many results may be returned Herewe customize the pagination by setting the page size to be an application parameterpostsPerPage Second it does sorting according to the user request And finally it feedsthe paginated and sorted data to widgets or view code for presentation

28 3 Post Management

After we finish with actionIndex() we modify the index view as follows Our change ismainly about adding the h1 header when the user specifies to display posts with a tag

ltphp if(empty($ GET[rsquotagrsquo])) gt

lth1gtPosts Tagged with ltigtltphp echo CHtmlencode($ GET[rsquotagrsquo]) gtltigtlth1gt

ltphp endif gt

ltphp $this-gtwidget(rsquoziiwidgetsCListViewrsquo array(

rsquodataProviderrsquo=gt$dataProvider

rsquoitemViewrsquo=gtrsquo viewrsquo

rsquotemplatersquo=gtitemsnpager)) gt

Note that in the above we use [CListView] to display the post list This widget requires apartial view to display the detail of each individual post Here we specify the partial viewto be view which means the file wwwrootblogprotectedviewspost viewphp In thisview script we can acccess the post instance being displayed via a local variable named$data

34 Managing Posts

Managing posts mainly refers to listing posts in an administrative view that allows us tosee posts with all statuses updating them and deleting them They are accomplished bythe admin operation and the delete operation respectively The code generated by yiic

does not need much modification Below we mainly explain how these two operations areimplemented

341 Listing Posts in Tabular View

The admin operation shows posts with all statuses in a tabular view The view supportssorting and pagination The following is the actionAdmin() method in PostController

public function actionAdmin()

$dataProvider=new CActiveDataProvider(rsquoPostrsquo array(

rsquosortrsquo=gtarray(

rsquodefaultOrderrsquo=gtrsquostatus update time DESCrsquo

)

))

$this-gtrender(rsquoadminrsquoarray(

rsquodataProviderrsquo=gt$dataProvider

))

34 Managing Posts 29

The above code is very similar to that in actionIndex() The main difference is in thequery criteria Here we do not specify any particular query criteria since we want to seeall posts We do customize the sort property of the data provider so that by default theposts are shown in the order of their statuses and update time

Below is the code for the admin view

lth1gtManage Postslth1gt

ltphp $this-gtwidget(rsquoziiwidgetsgridCGridViewrsquo array(

rsquodataProviderrsquo=gt$dataProvider

rsquocolumnsrsquo=gtarray(

array(

rsquonamersquo=gtrsquotitlersquo

rsquotypersquo=gtrsquorawrsquo

rsquovaluersquo=gtrsquoCHtmllink(CHtmlencode($data-gttitle) $data-gturl)rsquo

)

array(

rsquonamersquo=gtrsquostatusrsquo

rsquovaluersquo=gtrsquoLookupitem(PostStatus$data-gtstatus)rsquo

)

rsquocreate timedatetimersquo

rsquoupdate timedatetimersquo

array(

rsquoclassrsquo=gtrsquoCButtonColumnrsquo

)

)

)) gt

We use [CGridView] to display the posts It allows us to sort by a column and paginatethrough the posts if there are too many to be displayed in a single page Our change ismainly about how to display each column For example for the title column we specifythat it should be displayed as a hyperlink that points to the detailed view of the post

Tip When displaying text we call CHtmlencode() to encode HTML entities init This prevents from cross-site scripting attack

342 Deleting Posts

In the admin data grid there is a delete button in each row Clicking on the button shoulddelete the corresponding post Internally this triggers the delete action implemented asfollows

30 3 Post Management

public function actionDelete()

if(Yiiapp()-gtrequest-gtisPostRequest)

we only allow deletion via POST request

$this-gtloadModel()-gtdelete()

if(isset($ POST[rsquoajaxrsquo]))

$this-gtredirect(array(rsquoindexrsquo))

else

throw new CHttpException(400rsquoInvalid request Please do not repeat this request againrsquo)

The above code is the one generated by the yiic tool without any change We wouldlike to explain a little bit more about the checking on $ POST[rsquoajaxrsquo] The [CGridView]widget has a very nice feature that its sorting pagination and deletion operations are alldone in AJAX mode by default That means the whole page does not get reloaded if anyof the above operations is performed However it is also possible that the widget runs innon-AJAX mode (by setting its ajaxUpdate property to be false or disabling JavaScript onthe client side) It is necessary for the delete action to differentiate these two scenarios ifthe delete request is made via AJAX we should not redirect the user browser otherwisewe should

Deleting a post should also cause the deletion of all comments for that post In additionwe should also update the tbl tag table regarding the tags for the deleted post Both ofthese tasks can be achieved by writing an afterDelete method in the Post model class asfollows

protected function afterDelete()

parentafterDelete()

Commentmodel()-gtdeleteAll(rsquopost id=rsquo$this-gtid)

Tagmodel()-gtupdateFrequency($this-gttags rsquorsquo)

The above code is very straightforward it first deletes all those comments whose post id

is the same as the ID of the deleted post it then updates the tbl tag table for the tags

of the deleted post

34 Managing Posts 31

Tip We have to explicitly delete all comments for the deleted post here becauseSQLite does not really support foreign key constraints In a DBMS that supportsthis constraint (such as MySQL PostgreSQL) the foreign key constraint can be setup such that the DBMS automatically deletes the related comments if the post isdeleted In that case we no longer this explicit deletion call in our code

32 3 Post Management

Chapter 4

Comment Management

41 Customizing Comment Model

For the Comment model we mainly need to customize the rules() and attributeLabels()

methods The attributeLabels() method returns a mapping between attribute namesand attribute labels We do not need to touch relations() since the code generated bythe yiic tool is good enough

411 Customizing rules() Method

We first customize the validation rules generated by the yiic tool The following rules areused for comments

public function rules()

return array(

array(rsquocontent author emailrsquo rsquorequiredrsquo)

array(rsquoauthor email urlrsquo rsquolengthrsquo rsquomaxrsquo=gt128)

array(rsquoemailrsquorsquoemailrsquo)

array(rsquourlrsquorsquourlrsquo)

)

In the above we specify that the author email and content attributes are required thelength of author email and url cannot exceed 128 the email attribute must be a validemail address and the url attribute must be a valid URL

412 Customizing attributeLabels() Method

We then customize the attributeLabels() method to declare the label display for eachmodel attribute This method returns an array consisting of name-label pairs When wecall CHtmlactiveLabel() to display an attribute label

34 4 Comment Management

public function attributeLabels()

return array(

rsquoidrsquo =gt rsquoIdrsquo

rsquocontentrsquo =gt rsquoCommentrsquo

rsquostatusrsquo =gt rsquoStatusrsquo

rsquocreate timersquo =gt rsquoCreate Timersquo

rsquoauthorrsquo =gt rsquoNamersquo

rsquoemailrsquo =gt rsquoEmailrsquo

rsquourlrsquo =gt rsquoWebsitersquo

rsquopost idrsquo =gt rsquoPostrsquo

)

Tip If the label for an attribute is not declared in attributeLabels() an algo-rithm will be used to generate an appropriate label For example a label VerifyCode will be generated for attributes verify code or verifyCode

413 Customizing Saving Process

Because we want to record the creation time of a comment we override the beforeSave()

method of Comment like we do for the Post model

protected function beforeSave()

if(parentbeforeSave())

if($this-gtisNewRecord)

$this-gtcreate time=time()

return true

else

return false

42 Creating and Displaying Comments

In this section we implement the comment display and creation features

421 Displaying Comments

Instead of displaying and creating comments on individual pages we use the post detailpage (generated by the view action of PostController) Below the post content display

42 Creating and Displaying Comments 35

we display first a list of comments belonging to that post and then a comment creationform

In order to display comments on the post detail page we modify the view script wwwrootblogprotectedviewspostviewphp as follows

ltdiv id=commentsgt

ltphp if($model-gtcommentCountgt=1) gt

lth3gt

ltphp echo $model-gtcommentCount rsquocomment(s)rsquo gt

lth3gt

ltphp $this-gtrenderPartial(rsquo commentsrsquoarray(

rsquopostrsquo=gt$model

rsquocommentsrsquo=gt$model-gtcomments

)) gt

ltphp endif gt

ltdivgt

In the above we call renderPartial() to render a partial view named comments to displaythe list of comments belonging to the current post Note that in the view we use theexpression $model-gtcomments to retrieve the comments for the post This is valid becausewe have declared a comments relation in the Post class Evaluating this expression wouldtrigger an implicit JOIN database query to bring back the proper comments This featureis known as lazy relational query

The partial view comments is not very interesting It mainly goes through every commentand displays the detail of it Interested readers may refer to wwwrootyiidemosblog

protectedpost commentsphp

422 Creating Comments

To handle comment creation we first modify the actionView() method of PostController

as follows

public function actionView()

$post=$this-gtloadModel()

$comment=$this-gtnewComment($post)

$this-gtrender(rsquoviewrsquoarray(

rsquomodelrsquo=gt$post

rsquocommentrsquo=gt$comment

))

36 4 Comment Management

protected function newComment($post)

$comment=new Comment

if(isset($ POST[rsquoCommentrsquo]))

$comment-gtattributes=$ POST[rsquoCommentrsquo]

if($post-gtaddComment($comment))

if($comment-gtstatus==CommentSTATUS PENDING)

Yiiapp()-gtuser-gtsetFlash(rsquocommentSubmittedrsquorsquoThank yoursquo)

$this-gtrefresh()

return $comment

In the above we call the newComment() method before we render view In the newComment()

method we generate a Comment instance and check if the comment form is submitted If sowe try to add the comment for the post by calling $post-gtaddComment($comment) If it goesthrough we refresh the post detail page In case the comment needs to be approved we willshow a flash message to indicate this decision A flash message is usually a confirmationmessage displayed to end users If the user clicks on the refresh button of his browser themessage will disappear

We also need to modify wwwrootblogprotectedviewspostviewphp furthermore

ltdiv id=commentsgt

lth3gtLeave a Commentlth3gt

ltphp if(Yiiapp()-gtuser-gthasFlash(rsquocommentSubmittedrsquo)) gt

ltdiv class=successgt

ltphp echo Yiiapp()-gtuser-gtgetFlash(rsquocommentSubmittedrsquo) gt

ltdivgt

ltphp else gt

ltphp $this-gtrenderPartial(rsquocomment formrsquoarray(

rsquomodelrsquo=gt$comment

)) gt

ltphp endif gt

ltdivgtlt-- comments --gt

43 Managing Comments 37

In the above code we display the flash message if it is available If not we displaythe comment input form by rendering the partial view wwwrootblogprotectedviews

comment formphp

43 Managing Comments

Comment management includes updating deleting and approving comments These op-erations are implemented as actions in the CommentController class

431 Updating and Deleting Comments

The code generated by yiic for updating and deleting comments remains largely un-changed

432 Approving Comments

When comments are newly created they are in pending approval status and need to beapproved in order to be visible to guest users Approving a comment is mainly aboutchanging the status column of the comment

We create an actionApprove() method in CommentController as follows

public function actionApprove()

if(Yiiapp()-gtrequest-gtisPostRequest)

$comment=$this-gtloadModel()

$comment-gtapprove()

$this-gtredirect(array(rsquoindexrsquo))

else

throw new CHttpException(400rsquoInvalid requestrsquo)

In the above when the approve action is invoked via a POST request we call the approve()

method defined in the Comment model to change the status We then redirect the userbrowser to the page displaying the post that this comment belongs to

We also modify the actionIndex() method of Comment to show all comments We wouldlike to see comments pending approved to show up first

public function actionIndex()

38 4 Comment Management

$dataProvider=new CActiveDataProvider(rsquoCommentrsquo array(

rsquocriteriarsquo=gtarray(

rsquowithrsquo=gtrsquopostrsquo

rsquoorderrsquo=gtrsquotstatus tcreate time DESCrsquo

)

))

$this-gtrender(rsquoindexrsquoarray(

rsquodataProviderrsquo=gt$dataProvider

))

Notice that in the above code because both tbl post and tbl comment have columns statusand create time we need to disambiguate the corresponding column reference by prefixingthem with table alias names As described in the guide the alias for the primary table ina relational query is always t Therefore we are prefixing t to the status and create time

columns in the above code

Like the post index view the index view for CommentController uses [CListView] to displaythe comment list which in turn uses the partial view wwwrootblogprotectedviews

comment viewphp to display the detail of each individual comment We will not go intodetails here Interested readers may refer to the corresponding file in the blog demowwwrootyiidemosblogprotectedviewscomment viewphp

Chapter 5

Portlets

51 Customizing Page Layout

Features like rdquothe most recent commentsrdquo rdquotag cloudrdquo are better to be implemented inportlets A portlet is a pluggable user interface component that renders a fragment ofHTML code In this section we describe how to set up the portlet architecture for ourblog application

Before we go into details about developing portlets we first need to adjust our page layoutso that the portlets can be placed in a side bar

A page layout determines how to share common sections (eg header footer) amongdifferent pages and how to embed contents that are specific for different pages In Yiia page layout is represented as a view under the protectedviewslayouts folder Whenwe call the render() method in a controller the rendering result will be automaticallyembedded into this layout view before it is displayed to end users The default layoutview is named as main which means the wwwrootblogprotectedviewslayoutsmain

php file in our blog application

We could directly modify the main layout to add a side bar However doing so wouldmake some irrelevant pages such as about contact pages to have a side bar which is notalways desirable For this reason we need different layouts for different pages

We thus create two different layouts column1 and column2 The former displays the pagein a single column while the latter displays two columns in the page one of which is theside bar We will use column2 layout for the post and comment pages and column1 forthe rest of the pages Because both column1 and column2 still share the same header andfooter sections we let them to share the main layout

Based on the above description we create the column1 layout as follows

ltphp $this-gtbeginContent(rsquolayoutsmainrsquo) gt

ltdiv class=containergt

40 5 Portlets

ltdiv id=contentgt

ltphp echo $content gt

ltdivgtlt-- content --gt

ltdivgt

ltphp $this-gtendContent() gt

and the column2 layout as follows

ltphp $this-gtbeginContent(rsquolayoutsmainrsquo) gt

ltdiv class=containergt

ltdiv class=span-18gt

ltdiv id=contentgt

ltphp echo $content gt

ltdivgtlt-- content --gt

ltdivgt

ltdiv class=span-6 lastgt

ltdiv id=sidebargt

portlets to be placed here

ltdivgtlt-- sidebar --gt

ltdivgt

ltdivgt

ltphp $this-gtendContent() gt

As we can see both column1 and column2 are enclosed in a pair of beginContent andendContent calls They essentially use the specified view layoutsmain to decorate theenclosed content In all these layout views the variable $content refers to the dynamiccontent to be embedded into the layout

Besides customizing the layout view file we also need to adjust the CSS file wwwroot

blogcssmaincss so that the overall appearance would look like what we see in the blogdemo We will not go into details here

Info The skeleton application generated by the yiic tool uses the BluePrint CSSframework It provides a nice font reset and a robust grid system For more detailson how to use this framework please refer to its documentation

After creating the new layouts we need to set the layout property of controllers to makeuse of them So far we have three controllers SiteController PostController andCommentController We set SiteController to use column1 as the default layout whilethe rest two of the controllers use column2 for example

class PostController extends Controller

52 Creating User Menu Portlet 41

public $layout=rsquocolumn2rsquo

Tip If some view of the controller does not use the same layout as declaredby the layout property it may customize it in the view by a simple assignment$this-gtlayout=rsquonewlayoutrsquo

52 Creating User Menu Portlet

Based on the requirements analysis we need three different portlets the rdquouser menurdquoportlet the rdquotag cloudrdquo portlet and the rdquorecent commentsrdquo portlet We will develop theseportlets by extending the [CPortlet] widget provided by Yii

In this section we will develop our first concrete portlet - the user menu portlet whichdisplays a list of menu items that are only available to authenticated users The menucontains four items

bull Approve Comments a hyperlink that leads to a list of comments pending approval

bull Create New Post a hyperlink that leads to the post creation page

bull Manage Posts a hyperlink that leads to the post management page

bull Logout a link button that would log out the current user

521 Creating UserMenu Class

We create the UserMenu class to represent the logic part of the user menu portlet Theclass is saved in the file wwwrootblogprotectedcomponentsUserMenuphp which has thefollowing content

Yiiimport(rsquoziiwidgetsCPortletrsquo)

class UserMenu extends CPortlet

public function init()

$this-gttitle=CHtmlencode(Yiiapp()-gtuser-gtname)

parentinit()

42 5 Portlets

protected function renderContent()

$this-gtrender(rsquouserMenursquo)

The UserMenu class extends from the CPortlet class that we created previously It overridesboth the init() method and the renderContent() method of CPortlet The former setsthe portlet title to be the name of the current user the latter generates the portlet bodycontent by rendering a view named userMenu

Tip Notice that we have to explicitly include the CPortlet class by calling Yii

import() before we refer to it the first time This is because CPortlet is part of thezii project ndash the official extension library for Yii For performance considerationclasses in this project are not listed as core classes Therefore we have to import itbefore we use it the first time

522 Creating userMenu View

Next we create the userMenu view which is saved in the file wwwrootblogprotected

componentsviewsuserMenuphp

ltulgt

ltligtltphp echo CHtmllink(rsquoCreate New Postrsquoarray(rsquopostcreatersquo)) gtltligt

ltligtltphp echo CHtmllink(rsquoManage Postsrsquoarray(rsquopostadminrsquo)) gtltligt

ltligtltphp echo CHtmllink(rsquoApprove Commentsrsquoarray(rsquocommentindexrsquo))

rsquo (rsquo Commentmodel()-gtpendingCommentCount rsquo)rsquo gtltligt

ltligtltphp echo CHtmllink(rsquoLogoutrsquoarray(rsquositelogoutrsquo)) gtltligt

ltulgt

Info By default view files for a widget should be placed under the views sub-directory of the directory containing the widget class file The file name must bethe same as the view name

523 Using UserMenu Portlet

It is time for us to make use of our newly completed UserMenu portlet We modify thelayout view file wwwrootblogprotectedviewslayoutscolumn2php as follows

53 Creating Tag Cloud Portlet 43

ltdiv id=sidebargt

ltphp if(Yiiapp()-gtuser-gtisGuest) $this-gtwidget(rsquoUserMenursquo) gt

ltdivgt

In the above we call the widget() method to generate and execute an instance of theUserMenu class Because the portlet should only be displayed to authenticated users weonly call widget() when the isGuest property of the current user is false (meaning theuser is authenticated)

524 Testing UserMenu Portlet

Letrsquos test what we have so far

1 Open a browser window and enter the URL httpwwwexamplecomblogindex

php Verify that there is nothing displayed in the side bar section of the page

2 Click on the Login hyperlink and fill out the login form to login If successful verifythat the UserMenu portlet appears in the side bar and the portlet has the usernameas its title

3 Click on the rsquoLogoutrsquo hyperlink in the UserMenu portlet Verify that the logout actionis successful and the UserMenu portlet disappears

525 Summary

What we have created is a portlet that is highly reusable We can easily reuse it in adifferent project with little or no modification Moreover the design of this portlet followsclosely the philosophy that logic and presentation should be separated While we did notpoint this out in the previous sections such practice is used nearly everywhere in a typicalYii application

53 Creating Tag Cloud Portlet

Tag cloud displays a list of post tags with visual decorations hinting the popularity of eachindividual tag

531 Creating TagCloud Class

We create the TagCloud class in the file wwwrootblogprotectedcomponentsTagCloud

php The file has the following content

44 5 Portlets

Yiiimport(rsquoziiwidgetsCPortletrsquo)

class TagCloud extends CPortlet

public $title=rsquoTagsrsquo

public $maxTags=20

protected function renderContent()

$tags=Tagmodel()-gtfindTagWeights($this-gtmaxTags)

foreach($tags as $tag=gt$weight)

$link=CHtmllink(CHtmlencode($tag) array(rsquopostindexrsquorsquotagrsquo=gt$tag))

echo CHtmltag(rsquospanrsquo array(

rsquoclassrsquo=gtrsquotagrsquo

rsquostylersquo=gtfont-size$weightpt) $link)n

Unlike the UserMenu portlet the TagCloud portlet does not use a view Instead its presen-tation is done in the renderContent() method This is because the presentation does notcontain much HTML tags

We display each tag as a hyperlink to the post index page with the corresponding tagparameter The font size of each tag link is adjusted according to their relative weightamong other tags If a tag has higher frequency value than the other it will have a biggerfont size

532 Using TagCloud Portlet

Usage of the TagCloud portlet is very simple We modify the layout file wwwrootblog

protectedviewslayoutsmainphp as follows

ltdiv id=sidebargt

ltphp if(Yiiapp()-gtuser-gtisGuest) $this-gtwidget(rsquoUserMenursquo) gt

ltphp $this-gtwidget(rsquoTagCloudrsquo array(

rsquomaxTagsrsquo=gtYiiapp()-gtparams[rsquotagCloudCountrsquo]

)) gt

ltdivgt

54 Creating Recent Comments Portlet 45

54 Creating Recent Comments Portlet

In this section we create the last portlet that displays a list of comments recently pub-lished

541 Creating RecentComments Class

We create the RecentComments class in the file wwwrootblogprotectedcomponentsRecentCommentsphp The file has the following content

Yiiimport(rsquoziiwidgetsCPortletrsquo)

class RecentComments extends CPortlet

public $title=rsquoRecent Commentsrsquo

public $maxComments=10

public function getRecentComments()

return Commentmodel()-gtfindRecentComments($this-gtmaxComments)

protected function renderContent()

$this-gtrender(rsquorecentCommentsrsquo)

In the above we invoke the findRecentComments method which is defined in the Comment

class as follows

class Comment extends CActiveRecord

public function findRecentComments($limit=10)

return $this-gtwith(rsquopostrsquo)-gtfindAll(array(

rsquoconditionrsquo=gtrsquotstatus=rsquoselfSTATUS APPROVED

rsquoorderrsquo=gtrsquotcreate time DESCrsquo

rsquolimitrsquo=gt$limit

))

46 5 Portlets

542 Creating recentComments View

The recentComments view is saved in the file wwwrootblogprotectedcomponentsviews

recentCommentsphp It simply displays every comment returned by the RecentComments

getRecentComments() method

543 Using RecentComments Portlet

We modify the layout file wwwrootblogprotectedviewslayoutsmainphp to embedthis last portlet

ltdiv id=sidebargt

ltphp if(Yiiapp()-gtuser-gtisGuest) $this-gtwidget(rsquoUserMenursquo) gt

ltphp $this-gtwidget(rsquoTagCloudrsquo array(

rsquomaxTagsrsquo=gtYiiapp()-gtparams[rsquotagCloudCountrsquo]

)) gt

ltphp $this-gtwidget(rsquoRecentCommentsrsquo array(

rsquomaxCommentsrsquo=gtYiiapp()-gtparams[rsquorecentCommentCountrsquo]

)) gt

ltdivgt

Chapter 6

Final Work

61 Beautifying URLs

The URLs linking various pages of our blog application currently look ugly For examplethe URL for the page showing a post looks like the following

indexphpr=postshowampid=1

In this section we describe how to beautify these URLs and make them SEO-friendlyOur goal is to be able to use the following URLs in the application

1 indexphppostsyii leads to the page showing a list of posts with tag yii

2 indexphppost2A+Test+Post leads to the page showing the detail of the postwith ID 2 whose title is A Test Post

3 indexphppostupdate1 leads to the page that allows updating the post with ID1

Note that in the second URL format we include the post title in the URL This is mainlyto make the URL SEO friendly It is said that search engines may also respect the wordsfound in a URL when it is being indexed

To achieve our goal we modify the application configuration as follows

return array(

rsquocomponentsrsquo=gtarray(

rsquourlManagerrsquo=gtarray(

rsquourlFormatrsquo=gtrsquopathrsquo

rsquorulesrsquo=gtarray(

48 6 Final Work

rsquopostltidd+gtlttitlegtrsquo=gtrsquopostviewrsquorsquopostslttaggtrsquo=gtrsquopostindexrsquo

)

)

)

)

In the above we configure the urlManager component by setting its urlFormat propertyto be path and adding a set of rules

The rules are used by urlManager to parse and create the URLs in the desired formatFor example the first rule says that if a URL indexphppostsyii is requested theurlManager component should be responsible to dispatch the request to the route post

index and generate a tag GET parameter with the value yii On the other hand whencreating a URL with the route postindex and parameter tag the urlManager componentwill also use this rule to generate the desired URL indexphppostsyii For this reasonwe say that urlManager is a two-way URL manager

The urlManager component can further beautify our URLs such as hiding indexphp inthe URLs appending suffix like html to the URLs We can obtain these features easilyby configuring various properties of urlManager in the application configuration For moredetails please refer to the Guide

62 Logging Errors

A production Web application often needs sophisticated logging for various events In ourblog application we would like to log the errors occurring when it is being used Sucherrors could be programming mistakes or usersrsquo misuse of the system Logging these errorswill help us to improve the blog application

We enable the error logging by modifying the application configuration as follows

return array(

rsquopreloadrsquo=gtarray(rsquologrsquo)

rsquocomponentsrsquo=gtarray(

rsquologrsquo=gtarray(

rsquoclassrsquo=gtrsquoCLogRouterrsquo

rsquoroutesrsquo=gtarray(

array(

rsquoclassrsquo=gtrsquoCFileLogRoutersquo

rsquolevelsrsquo=gtrsquoerror warningrsquo

63 Final Tune-up and Deployment 49

)

)

)

)

)

With the above configuration if an error or warning occurs detailed information will belogged and saved in a file located under the directory wwwrootblogprotectedruntime

The log component offers more advanced features such as sending log messages to a listof email addresses displaying log messages in JavaScript console window etc For moredetails please refer to the Guide

63 Final Tune-up and Deployment

We are close to finish our blog application Before deployment we would like to do sometune-ups

631 Changing Home Page

We change to use the post list page as the home page We modify the application config-uration as follows

return array(

rsquodefaultControllerrsquo=gtrsquopostrsquo

)

Tip Because PostController already declares list to be its default action whenwe access the home page of the application we will see the result generated by thelist action of the post controller

632 Enabling Schema Caching

Because ActiveRecord relies on the metadata about tables to determine the column infor-mation it takes time to read the metadata and analyze it This may not be a problemduring development stage but for an application running in production mode it is a totalwaste of time if the database schema does not change Therefore we should enable theschema caching by modifying the application configuration as follows

50 6 Final Work

return array(

rsquocomponentsrsquo=gtarray(

rsquocachersquo=gtarray(

rsquoclassrsquo=gtrsquoCDbCachersquo

)

rsquodbrsquo=gtarray(

rsquoclassrsquo=gtrsquosystemdbCDbConnectionrsquo

rsquoconnectionStringrsquo=gtrsquosqlitewwwrootblogprotecteddatablogdbrsquo

rsquoschemaCachingDurationrsquo=gt3600

)

)

)

In the above we first add a cache component which uses a default SQLite database as thecaching storage If our server is equipped with other caching extensions such as APCwe could change to use them as well We also modify the db component by setting itsschemaCachingDuration property to be 3600 which means the parsed database schemadata can remain valid in cache for 3600 seconds

633 Disabling Debugging Mode

We modify the entry script file wwwrootblogindexphp by removing the line definingthe constant YII DEBUG This constant is useful during development stage because it allowsYii to display more debugging information when an error occurs However when theapplication is running in production mode displaying debugging information is not agood idea because it may contain sensitive information such as where the script file islocated and the content in the file etc

634 Deploying the Application

The final deployment process manly involves copying the directory wwwrootblog to thetarget directory The following checklist shows every needed step

1 Install Yii in the target place if it is not available

2 Copy the entire directory wwwrootblog to the target place

3 Edit the entry script file indexphp by pointing the $yii variable to the new Yiibootstrap file

4 Edit the file protectedyiicphp by setting the $yiic variable to be the new Yiiyiicphp file

64 Future Enhancements 51

5 Change the permission of the directories assets and protectedruntime so that theyare writable by the Web server process

64 Future Enhancements

641 Using a Theme

Without writing any code our blog application is already themeable To use a themewe mainly need to develop the theme by writing customized view files in the theme Forexample to use a theme named classic that uses a different page layout we would createa layout view file wwwrootblogthemesclassicviewslayoutsmainphp We also needto change the application configuration to indicate our choice of the classic theme

return array(

rsquothemersquo=gtrsquoclassicrsquo

)

642 Internationalization

We may also internationalize our blog application so that its pages can be displayed indifferent languages This mainly involves efforts in two aspects

First we may create view files in different languages For example for the list pageof PostController we can create a view file wwwrootblogprotectedviewspostzh cn

listphp When the application is configured to use simplified Chinese (the language codeis zh cn) Yii will automatically use this new view file instead of the original one

Second we may create message translations for those messages generated by code Themessage translations should be saved as files under the directory wwwrootblogprotected

messages We also need to modify the code where we use text strings by enclosing themin the method call Yiit()

For more details about internationalization please refer to the Guide

643 Improving Performance with Cache

While the Yii framework itself is very efficient it is not necessarily true that an applicationwritten in Yii efficient There are several places in our blog application that we can improvethe performance For example the tag clould portlet could be one of the performancebottlenecks because it involves complex database query and PHP logic

52 6 Final Work

We can make use of the sophisticated caching feature provided by Yii to improve theperformance One of the most useful components in Yii is COutputCache which caches afragment of page display so that the underlying code generating the fragment does not needto be executed for every request For example in the layout file wwwrootblogprotected

viewslayoutsmainphp we can enclose the tag cloud portlet with COutputCache

ltphp if($this-gtbeginCache(rsquotagCloudrsquo array(rsquodurationrsquo=gt3600))) gt

ltphp $this-gtwidget(rsquoTagCloudrsquo array(

rsquomaxTagsrsquo=gtYiiapp()-gtparams[rsquotagCloudCountrsquo]

)) gt

ltphp $this-gtendCache() gt

With the above code the tag cloud display will be served from cache instead of beinggenerated on-the-fly for every request The cached content will remain valid in cache for3600 seconds

644 Adding New Features

Our blog application only has very basic functionalities To become a complete blogsystem more features are needed for example calendar portlet email notifications postcategorization archived post portlet and so on We will leave the implementation of thesefeatures to interested readers

  • Contents
  • License
  • Getting Started
    • Building a Blog System using Yii
    • Testdriving with Yii
      • Installing Yii
      • Creating Skeleton Application
      • Application Workflow
        • Requirements Analysis
        • Overall Design
          • Initial Prototyping
            • Setting Up Database
              • Creating Database
              • Establishing Database Connection
                • Scaffolding
                • Authenticating User
                • Summary
                  • Post Management
                    • Customizing Post Model
                      • Customizing rules() Method
                      • Customizing relations() Method
                      • Representing Status in Text
                        • Creating and Updating Posts
                          • Customizing Access Control
                          • Customizing create and update Operations
                            • Displaying Posts
                              • Customizing view Operation
                              • Customizing index Operation
                                • Managing Posts
                                  • Listing Posts in Tabular View
                                  • Deleting Posts
                                      • Comment Management
                                        • Customizing Comment Model
                                          • Customizing rules() Method
                                          • Customizing attributeLabels() Method
                                          • Customizing Saving Process
                                            • Creating and Displaying Comments
                                              • Displaying Comments
                                              • Creating Comments
                                                • Managing Comments
                                                  • Updating and Deleting Comments
                                                  • Approving Comments
                                                      • Portlets
                                                        • Customizing Page Layout
                                                        • Creating User Menu Portlet
                                                          • Creating UserMenu Class
                                                          • Creating userMenu View
                                                          • Using UserMenu Portlet
                                                          • Testing UserMenu Portlet
                                                          • Summary
                                                            • Creating Tag Cloud Portlet
                                                              • Creating TagCloud Class
                                                              • Using TagCloud Portlet
                                                                • Creating Recent Comments Portlet
                                                                  • Creating RecentComments Class
                                                                  • Creating recentComments View
                                                                  • Using RecentComments Portlet
                                                                      • Final Work
                                                                        • Beautifying URLs
                                                                        • Logging Errors
                                                                        • Final Tune-up and Deployment
                                                                          • Changing Home Page
                                                                          • Enabling Schema Caching
                                                                          • Disabling Debugging Mode
                                                                          • Deploying the Application
                                                                            • Future Enhancements
                                                                              • Using a Theme
                                                                              • Internationalization
                                                                              • Improving Performance with Cache
                                                                              • Adding New Features
Page 6: Yii Blog 1.1.0 Demo Tutor

License of Yii

The Yii framework is free software It is released under the terms of the following BSDLicense

Copyright ccopy2008-2009 by Yii Software LLC All rights reserved

Redistribution and use in source and binary forms with or without modification arepermitted provided that the following conditions are met

1 Redistributions of source code must retain the above copyright notice this list ofconditions and the following disclaimer

2 Redistributions in binary form must reproduce the above copyright notice this list ofconditions and the following disclaimer in the documentation andor other materialsprovided with the distribution

3 Neither the name of Yii Software LLC nor the names of its contributors may beused to endorse or promote products derived from this software without specificprior written permission

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS rdquoAS

ISrdquo AND ANY EXPRESS OR IMPLIED WARRANTIES INCLUDING BUT NOT LIMITED TO THE

IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PUR-

POSE ARE DISCLAIMED IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBU-

TORS BE LIABLE FOR ANY DIRECT INDIRECT INCIDENTAL SPECIAL EXEMPLARY OR

CONSEQUENTIAL DAMAGES (INCLUDING BUT NOT LIMITED TO PROCUREMENT OF SUB-

STITUTE GOODS OR SERVICES LOSS OF USE DATA OR PROFITS OR BUSINESS INTERRUP-

TION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY WHETHER IN CONTRACT

STRICT LIABILITY OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY

WAY OUT OF THE USE OF THIS SOFTWARE EVEN IF ADVISED OF THE POSSIBILITY OF

SUCH DAMAGE

vi Contents

Chapter 1

Getting Started

11 Building a Blog System using Yii

This tutorial describes how to use Yii to develop a blog application shown as the blog demowhich can be found in the Yii release files It explains in detail every step to be takenduring the development which may also be applied in developing other Web applicationsAs a complement to the Guide and the Class Reference of Yii this tutorial aims to showpractical usage of Yii instead of thorough and definitive description

Readers of this tutorial are not required to have prior knowledge about Yii However basicknowledge of object-oriented programming (OOP) and database programming would helpreaders to understand the tutorial more easily

This tutorial is released under the Terms of Yii Documentation

12 Testdriving with Yii

In this section we describe how to create a skeleton application that will serve as ourstarting point For simplicity we assume that the document root of our Web server iswwwroot and the corresponding URL is httpwwwexamplecom

121 Installing Yii

We first install the Yii framework Grab a copy of the Yii release file (version 110 orabove) from wwwyiiframeworkcom and unpack it to the directory wwwrootyii Doublecheck to make sure that there is a directory wwwrootyiiframework

Tip The Yii framework can be installed anywhere in the file system not necessarilyunder a Web folder Its framework directory contains all framework code and isthe only framework directory needed when deploying an Yii application A singleinstallation of Yii can be used by multiple Yii applications

2 1 Getting Started

After installing Yii open a browser window and access the URL httpwwwexample

comyiirequirementsindexphp It shows the requirement checker provided in the Yiirelease For our blog application besides the minimal requirements needed by Yii we alsoneed to enable both the pdo and pdo sqlite PHP extensions so that we can access SQLitedatabases

122 Creating Skeleton Application

We then use the yiic tool to create a skeleton application under the directory wwwroot

blog The yiic tool is a command line tool provided in the Yii release It can be used togenerate code to reduce certain repetitive coding tasks

Open a command window and execute the following command

wwwrootyiiframeworkyiic webapp wwwrootblog

Create a Web application under rsquowwwrootblogrsquo [Yes|No]y

Tip In order to use the yiic tool as shown above the CLI PHP program must beon the command search path If not the following command may be used instead

pathtophp wwwrootyiiframeworkyiicphp webapp wwwrootblog

To try out the application we just created open a Web browser and navigate to the URLhttpwwwexamplecomblogindexphp We should see that our skeleton applicationalready has four fully functional pages the homepage the about page the contact pageand the login page

In the following we briefly describe what we have in this skeleton application

Entry Script

We have an entry script file wwwrootblogindexphp which has the following content

ltphp

$yii=rsquowwwrootframeworkyiiphprsquo

$config=dirname( FILE )rsquoprotectedconfigmainphprsquo

remove the following line when in production mode

defined(rsquoYII DEBUGrsquo) or define(rsquoYII DEBUGrsquotrue)

12 Testdriving with Yii 3

require once($yii)

YiicreateWebApplication($config)-gtrun()

This is the only script that Web users can directly access The script first includes theYii bootstrap file yiiphp It then creates an application instance with the specifiedconfiguration and executes the application

Base Application Directory

We also have an application base directory wwwrootblogprotected The majority of ourcode and data will be placed under this directory and it should be protected from beingaccessed by Web users For Apache httpd Web server we place under this directory ahtaccess file with the following content

deny from all

For other Web servers please refer to the corresponding manual on how to protect adirectory from being accessed by Web users

123 Application Workflow

To help understand how Yii works we describe the main workflow in our skeleton appli-cation when a user is accessing its contact page

1 The user requests the URL httpwwwexamplecomblogindexphpr=sitecontact

2 The entry script is executed by the Web server to process the request

3 An application instance is created and configured with initial property values speci-fied in the application configuration file wwwrootblogprotectedconfigmainphp

4 The application resolves the request into a controller and a controller action For thecontact page request it is resolved as the site controller and the contact action (theactionContact method in wwwrootblogprotectedcontrollersSiteControllerphp)

5 The application creates the site controller in terms of a SiteController instanceand then executes it

6 The SiteController instance executes the contact action by calling its actionContact()method

4 1 Getting Started

7 The actionContact method renders a view named contact to the Web user Inter-nally this is achieved by including the view file wwwrootblogprotectedviews

sitecontactphp and embedding the result into the layout file wwwrootblog

protectedviewslayoutsmainphp

13 Requirements Analysis

The blog system that we are going to develop is a single user system The owner of thesystem will be able to perform the following actions

bull Login and logout

bull Create update and delete posts

bull Publish unpublish and archive posts

bull Approve and delete comments

All other users are guest users who can perform the following actions

bull Read posts

bull Create comments

Additional Requirements for this system include

bull The homepage of the system should display a list of the most recent posts

bull If a page contains more than 10 posts they should be displayed in pages

bull The system should display a post together with its comments

bull The system should be able to list posts with a specified tag

bull The system should show a cloud of tags indicating their use frequencies

bull The system should show a list of most recent comments

bull The system should be themeable

bull The system should use SEO-friendly URLs

14 Overall Design 5

14 Overall Design

Based on the analysis of the requirements we decide to use the following database tablesto store the persistent data for our blog application

bull tbl user stores the user information including username and password

bull tbl post stores the blog post information It mainly consists of the following columns

ndash title required title of the post

ndash content required body content of the post which uses the Markdown format

ndash status required status of the post which can be one of following values

lowast 1 meaning the post is in draft and is not visible to public

lowast 2 meaning the post is published to public

lowast 3 meaning the post is outdated and is not visible in the post list (stillaccessible individually though)

ndash tags optional a list of comma-separated words categorizing the post

bull tbl comment stores the post comment information Each comment is associated witha post and mainly consists of the following columns

ndash name required the author name

ndash email required the author email

ndash website optional the author website URL

ndash content required the comment content in plain text format

ndash status required status of the comment which indicates whether the commentis approved (value 2) or not (value 1)

bull tbl tag stores post tag frequency information that is needed to implement the tagcloud feature The table mainly contains the following columns

ndash name required the unique tag name

ndash frequency required the number of times that the tag appears in posts

bull tbl lookup stores generic lookup information It is essentially a map between integervalues and text strings The former is the data representation in our code while thelatter is the corresponding presentation to end users For example we use integer1 to represent the draft post status and string Draft to display this status to endusers This table mainly contains the following columns

6 1 Getting Started

ndash name the textual representation of the data item that is to be displayed to endusers

ndash code the integer representation of the data item

ndash type the type of the data item

ndash position the relative display order of the data item among other items of thesame type

The following entity-relation (ER) diagram shows the table structure and relationshipsabout the above tables

Figure 11 Entity-Relation Diagram of the Blog Database

Complete SQL statements corresponding to the above ER diagram may be found inthe blog demo In our Yii installation they are in the file wwwrootyiidemosblog

protecteddataschemasqlitesql

Info We name all our table names and column names in lower case This is becausedifferent DBMS often have different case-sensitivity treatment and we want to avoidtroubles like this

We also prefix all our tables with tbl This serves for two purposes First the prefixintroduces a namespace to these tables in case when they need to coexist with othertables in the same database which often happens in a shared hosting environmentwhere a single database is being used by multiple applications Second using tableprefix reduces the possibility of having some table names that are reserved keywordsin DBMS

14 Overall Design 7

We divide the development of our blog application into the following milestones

bull Milestone 1 creating a prototype of the blog system It should consist of most ofthe required functionalities

bull Milestone 2 completing post management It includes creating listing showingupdating and deleting posts

bull Milestone 3 completing comment management It includes creating listing ap-proving updating and deleting post comments

bull Milestone 4 implementing portlets It includes user menu login tag cloud andrecent comments portlets

bull Milestone 5 final tune-up and deployment

8 1 Getting Started

Chapter 2

Initial Prototyping

21 Setting Up Database

Having created a skeleton application and finished the database design in this section wewill create the blog database and establish the connection to it in the skeleton application

211 Creating Database

We choose to create a SQLite database Because the database support in Yii is built on topof PDO we can easily switch to use a different type of DBMS (eg MySQL PostgreSQL)without the need to change our application code

We create the database file blogdb under the directory wwwrootblogprotecteddataNote that both the directory and the database file have to be writable by the Web serverprocess as required by SQLite We may simply copy the database file from the blogdemo in our Yii installation which is located at wwwrootyiidemosblogprotecteddatablogdb We may also generate the database by executing the SQL statements in the filewwwrootyiidemosblogprotecteddataschemasqlitesql

Tip To execute SQL statements we may use the sqlite3 command line tool thatcan be found in the SQLite official website

212 Establishing Database Connection

To use the blog database in the skeleton application we created we need to modify itsapplication configuration which is stored in the PHP script wwwrootblogprotected

configmainphp The script returns an associative array consisting of name-value pairseach of which is used to initialize a writable property of the application instance

We configure the db component as follows

10 2 Initial Prototyping

return array(

rsquocomponentsrsquo=gtarray(

rsquodbrsquo=gtarray(

rsquoconnectionStringrsquo=gtrsquosqlitewwwrootblogprotecteddatablogdbrsquo

rsquotablePrefixrsquo=gtrsquotbl rsquo

)

)

)

The above configuration says that we have a db application component whose connectionStringproperty should be initialized as sqlitewwwrootblogprotecteddatablogdb and whosetablePrefix property should be tbl

With this configuration we can access the DB connection object using Yiiapp()-gtdb

at any place in our code Note that Yiiapp() returns the application instance that wecreate in the entry script If you are interested in possible methods and properties that theDB connection has you may refer to its class reference However in most cases we are notgoing to use this DB connection directly Instead we will use the so-called ActiveRecordto access the database

We would like to explain a bit more about the tablePrefix property that we set in theconfiguration This tells the db connection that it should respect the fact we are usingtbl as the prefix to our database table names In particular if in a SQL statement thereis a token enclosed within double curly brackets (eg post then the db connectionshould translate it into a name with the table prefix (eg tbl post) before sending it toDBMS for execution This feature is especially useful if in future we need to modify thetable name prefix without touching our source code For example if we are developing ageneric content management system (CMS) we may exploit this feature so that when itis being installed in a new environment we can allow users to choose a table prefix theylike

22 Scaffolding 11

Tip If you want to use MySQL instead of SQLite to store data you may cre-ate a MySQL database named blog using the SQL statements in wwwrootyii

demosblogprotecteddataschemamysqlsql Then modify the applicationconfiguration as follows

return array(

rsquocomponentsrsquo=gtarray(

rsquodbrsquo=gtarray(

rsquoconnectionStringrsquo =gt rsquomysqlhost=localhostdbname=blogrsquo

rsquoemulatePreparersquo =gt true

rsquousernamersquo =gt rsquorootrsquo

rsquopasswordrsquo =gt rsquorsquo

rsquocharsetrsquo =gt rsquoutf8rsquo

rsquotablePrefixrsquo =gt rsquotbl rsquo

)

)

)

22 Scaffolding

Create read update and delete (CRUD) are the four basic operations of data objects in anapplication Because the task of implementing the CRUD operations is so common whendeveloping Web applications Yii provides a tool to automate this process (also known asscaffolding) In this section we will describe how to use the tool to implement CRUD forposts and comments

Open a command window and run the following commands

wwwrootyiiframeworkyiic shell wwwrootblogindexphp

Yii Interactive Tool v11

Please type rsquohelprsquo for help Type rsquoexitrsquo to quit

gtgt model

gtgt crud Post

gtgt crud Comment

gtgt exit

12 2 Initial Prototyping

Info Some PHP installations may use a different phpini file for command line(CLI) PHP parser As a result when running the above yiic commands youmay encounter errors like rdquoYiiBaseinclude(PDOphp) failed to open streamrdquoor rdquocould not find driverrdquo Please double check your CLI PHP configuration byexecuting the following command

php -r phpinfo()

The result of the above command will show which phpini file is being used andwhich extensions are loaded If a wrong phpini file is used you may use thefollowing command to explicitly specify the correct phpini to use

php -c phpini wwwrootyiiframeworkyiicphp shell wwwrootblogindexphp

The commands above accomplish two tasks First the model command generates a modelclass file for each table in the blog database Second the crud commands generate thecode needed by the CRUD operations for the Post and Comment models

Tip The command model generates a model class for every table in the databaseSometimes we probably donrsquot want to do so (eg the database contains some irrel-evant tables) In this case we can create model classes one by one For exampleto create the User model we can use the command model User The model com-mand also has some more advanced usages For more details execute help model

command

We can test the generated code by accessing the following URLs

httpwwwexamplecomblogindexphpr=post

httpwwwexamplecomblogindexphpr=comment

Notice that the post and comment features implemented by the generated code are com-pletely independent of each other Also when creating a new post or comment we arerequired to enter information such as author id and create time which in real applica-tion should be set by the program Donrsquot worry We will fix these problems in the nextmilestones For now we should be fairly satisfied as this prototype already contains mostfeatures that we need to implement for the blog application

To prepare for the next milestones letrsquos take a closer look at the files generated by theabove commands All the files are generated under wwwrootblogprotected For conve-nience we group them into model files controller files and view files

22 Scaffolding 13

bull model files

ndash modelsUserphp contains the User class that extends from CActiveRecord andcan be used to access the tbl user database table

ndash modelsPostphp contains the Post class that extends from CActiveRecord andcan be used to access the tbl post database table

ndash modelsTagphp contains the Tag class that extends from CActiveRecord andcan be used to access the tbl tag database table

ndash modelsCommentphp contains the Comment class that extends from CActiveRe-cord and can be used to access the tbl comment database table

ndash modelsLookupphp contains the Lookup class that extends from CActiveRecordand can be used to access the tbl lookup database table

bull controller file

ndash controllersPostControllerphp contains the PostController class which is thecontroller in charge of all CRUD operations about posts

ndash controllersCommentControllerphp contains the CommentController class whichis the controller in charge of all CRUD operations about comments

bull view files

ndash viewspostcreatephp is the view file that shows an HTML form to create anew post

ndash viewspostupdatephp is the view file that shows an HTML form to update anexisting post

ndash viewspostviewphp is the view file that displays the detailed information of apost

ndash viewspostindexphp is the view file that displays a list of posts

ndash viewspostadminphp is the view file that displays posts in a table with ad-ministrative commands

ndash viewspost formphp is the partial view file embedded in viewspostcreate

php and viewspostupdatephp It displays the HTML form for collecting postinformation

ndash viewspost viewphp is the partial view file used by viewspostindexphp Itdisplays the brief view of a single post

ndash a similar set of view files are also generated for comment

14 2 Initial Prototyping

In order to understand better how the above files are used we show in the following theworkflow that occurs in the blog application when displaying a list of posts

1 The user requests the URL httpwwwexamplecomblogindexphpr=posts

2 The entry script is executed by the Web server which creates and initializes anapplication instance to handle the request

3 The application creates an instance of PostController and executes it

4 The PostController instance executes the index action by calling its actionIndex()

method Note that index is the default action if the user does not specify an actionto execute in the URL

5 The actionIndex() method queries database to bring back the list of recent posts

6 The actionIndex() method renders the index view with the post data

23 Authenticating User

Our blog application needs to differentiate between the system owner and guest usersTherefore we need to implement the user authentication feature

As you may have found that the skeleton application already provides user authenticationby checking if the username and password are both demo or admin In this section wewill modify the corresponding code so that the authentication is done against the User

database table

User authentication is performed in a class implementing the IUserIdentity interface Theskeleton application uses the UserIdentity class for this purpose The class is stored inthe file wwwrootblogprotectedcomponentsUserIdentityphp

Tip By convention the name of a class file must be the same as the correspondingclass name suffixed with the extension php Following this convention one canrefer to a class using a path alias For example we can refer to the UserIdentity

class with the alias applicationcomponentsUserIdentity Many APIs in Yiican recognize path aliases (eg YiicreateComponent()) and using path aliasesavoids the necessity of embedding absolute file paths in the code The existence ofthe latter often causes trouble when we deploy an application

We modify the UserIdentity class as follows

23 Authenticating User 15

ltphp

class UserIdentity extends CUserIdentity

private $ id

public function authenticate()

$username=strtolower($this-gtusername)

$user=Usermodel()-gtfind(rsquoLOWER(username)=rsquoarray($username))

if($user===null)

$this-gterrorCode=selfERROR USERNAME INVALID

else if($user-gtvalidatePassword($this-gtpassword))

$this-gterrorCode=selfERROR PASSWORD INVALID

else

$this-gt id=$user-gtid

$this-gtusername=$user-gtusername

$this-gterrorCode=selfERROR NONE

return $this-gterrorCode==selfERROR NONE

public function getId()

return $this-gt id

In the authenticate() method we use the User class to look for a row in the User tablewhose username column is the same as the given username in a case-insensitive mannerRemember that the User class was created using the yiic tool in the prior section Becausethe User class extends from CActiveRecord we can exploit the ActiveRecord feature toaccess the User table in an OOP fashion

In order to check if the user has entered a valid password we invoke the validatePassword

method of the User class We need to modify the file wwwrootblogprotectedmodels

Userphp as follows Note that instead of storing the plain password in the database westore the hash result of the password and a randomly generated salt key When validatingthe user-entered password we should compare the hash results instead

class User extends CActiveRecord

public function validatePassword($password)

return $this-gthashPassword($password$this-gtsalt)===$this-gtpassword

16 2 Initial Prototyping

public function hashPassword($password$salt)

return md5($salt$password)

In the UserIdentity class we also override the getId() method which returns the id valueof the user found in the User table The parent implementation would return the usernameinstead Both the username and id properties will be stored in the user session and maybe accessed via Yiiapp()-gtuser from anywhere in our code

Tip In the UserIdentity class we reference the class CUserIdentity without ex-plicitly including the corresponding class file This is because CUserIdentity is acore class provided by the Yii framework Yii will automatically include the classfile for any core class when it is referenced for the first time

We also do the same with the User class This is because the User class file is placedunder the directory wwwrootblogprotectedmodels which has been added tothe PHP include path according to the following lines found in the applicationconfiguration

return array(

rsquoimportrsquo=gtarray(

rsquoapplicationmodelsrsquo

rsquoapplicationcomponentsrsquo

)

)

The above configuration says that any class whose class file is located under either wwwrootblogprotectedmodels or wwwrootblogprotectedcomponents willbe automatically included when the class is referenced for the first time

The UserIdentity class is mainly used by the LoginForm class to authenticate a user basedon the username and password input collected from the login page The following codefragment shows how UserIdentity is used

$identity=new UserIdentity($username$password)

$identity-gtauthenticate()

switch($identity-gterrorCode)

24 Summary 17

case UserIdentityERROR NONE

Yiiapp()-gtuser-gtlogin($identity)

break

Info People often get confused about identity and the user application componentThe former represents a way of performing authentication while the latter is usedto represent the information related with the current user An application can onlyhave one user component but it can have one or several identity classes dependingon what kind of authentication it supports Once authenticated an identity instancemay pass its state information to the user component so that they are globallyaccessible via user

To test the modified UserIdentity class we can browse the URL httpwwwexample

comblogindexphp and try logging in with the username and password that we store inthe User table If we use the database provided by the blog demo we should be able tologin with username demo and password demo Note that this blog system does not providethe user management feature As a result a user cannot change his account or create anew one through the Web interface The user management feature may be considered asa future enhancement to the blog application

24 Summary

We have completed the milestone 1 Letrsquos summarize what we have done so far

1 We identified the requirements to be fulfilled

2 We installed the Yii framework

3 We created a skeleton application

4 We designed and created the blog database

5 We modified the application configuration by adding the database connection

6 We generated the code that implements the basic CRUD operations for both postsand comments

7 We modified the authentication method to check against the tbl user table

18 2 Initial Prototyping

For a new project most of the time will be spent in step 1 and 4 for this first milestone

Although the code generated by the yiic tool implements fully functional CRUD oper-ations for a database table it often needs to be modified in practical applications Forthis reason in the next two milestone our job is to customize the generated CRUD codeabout posts and comments so that it reaches our initial requirements

In general we first modify the model class file by adding appropriate validation rules anddeclaring relational objects We then modify the controller action and view code for eachindividual CRUD operation

Chapter 3

Post Management

31 Customizing Post Model

The Post model class generated by the yiic tool mainly needs to be modified in two places

bull the rules() method specifies the validation rules for the model attributes

bull the relations() method specifies the related objects

Info A model consists of a list of attributes each associated with a column in thecorresponding database table Attributes can be declared explicitly as class membervariables or implicitly without any declaration

311 Customizing rules() Method

We first specify the validation rules which ensure the attribute values entered by usersare correct before they are saved to the database For example the status attribute ofPost should be an integer 1 2 or 3 The yiic tool also generates validation rules for eachmodel However these rules are based on the table column information and may not beappropriate

Based on the requirement analysis we modify the rules() method as follows

public function rules()

return array(

array(rsquotitle content statusrsquo rsquorequiredrsquo)

array(rsquotitlersquo rsquolengthrsquo rsquomaxrsquo=gt128)

array(rsquostatusrsquo rsquoinrsquo rsquorangersquo=gtarray(123))

array(rsquotagsrsquo rsquomatchrsquo rsquopatternrsquo=gtrsquo^[ws]+$rsquorsquomessagersquo=gtrsquoTags can only contain word charactersrsquo)

20 3 Post Management

array(rsquotagsrsquo rsquonormalizeTagsrsquo)

)

In the above we specify that the title content and status attributes are required thelength of title should not exceed 128 the status attribute value should be 1 (draft) 2(published) or 3 (archived) and the tags attribute should only contain word charactersand commas In addition we use normalizeTags to normalize the user-entered tags so thatthe tags are unique and properly separated with commas

The validators such as required length in and match are all built-in validators providedby Yii The normalizeTags validator is a method-based validator that we need to definein the Post class For more information about how to specify validation rules please referto the Guide

public function normalizeTags($attribute$params)

$this-gttags=Tagarray2string(array unique(Tagstring2array($this-gttags)))

The rules declared in this method are executed one by one when we call the validate() orsave() method of the model instance

Note It is very important to remember that attributes appearing in rules() mustbe those to be entered by end users Other attributes such as id and create time

in the Post model which are set by our code or database should not be in rules()For more details please refer to Securing Attribute Assignments

After making these changes we can visit the post creation page again to verify that thenew validation rules are taking effect

312 Customizing relations() Method

Lastly we customize the relations() method to specify the related objects of a post Bydeclaring these related objects in relations() we can exploit the powerful RelationalActiveRecord (RAR) feature to access the related object information of a post such asits author and comments without the need to write complex SQL JOIN statements

We customize the relations() method as follows

31 Customizing Post Model 21

public function relations()

return array(

rsquoauthorrsquo =gt array(selfBELONGS TO rsquoUserrsquo rsquoauthor idrsquo)

rsquocommentsrsquo =gt array(selfHAS MANY rsquoCommentrsquo rsquopost idrsquo

rsquoconditionrsquo=gtrsquocommentsstatus=rsquoCommentSTATUS APPROVED

rsquoorderrsquo=gtrsquocommentscreate time DESCrsquo)

rsquocommentCountrsquo =gt array(selfSTAT rsquoCommentrsquo rsquopost idrsquo

rsquoconditionrsquo=gtrsquostatus=rsquoCommentSTATUS APPROVED)

)

The above relations state that

bull A post belongs to an author whose class is User and the relationship is establishedbased on the author id attribute value of the post

bull A post has many comments whose class is Comment and the relationship is establishedbased on the post id attribute value of the comments These comments should besorted according to their creation time and the comments must be approved

bull The commentCount relation is a bit special as it returns back an aggregation resultwhich is about how many comments the post has

With the above relation declaration we can easily access the author and comments of apost like the following

$author=$post-gtauthor

echo $author-gtusername

$comments=$post-gtcomments

foreach($comments as $comment)

echo $comment-gtcontent

For more details about how to declare and use relations please refer to the Guide

313 Representing Status in Text

Because the status of a post is stored as an integer in the database we need to provide atextual representation so that it is more intuitive when being displayed to end users In alarge system the similar requirement is very common

22 3 Post Management

As a generic solution we use the tbl lookup table to store the mapping between integervalues and textual representations that are needed by other data objects We modify theLookup model class as follows to more easily access the textual data in the table

class Lookup extends CActiveRecord

private static $ items=array()

public static function items($type)

if(isset(self$ items[$type]))

selfloadItems($type)

return self$ items[$type]

public static function item($type$code)

if(isset(self$ items[$type]))

selfloadItems($type)

return isset(self$ items[$type][$code]) self$ items[$type][$code] false

private static function loadItems($type)

self$ items[$type]=array()

$models=selfmodel()-gtfindAll(array(

rsquoconditionrsquo=gtrsquotype=typersquo

rsquoparamsrsquo=gtarray(rsquotypersquo=gt$type)

rsquoorderrsquo=gtrsquopositionrsquo

))

foreach($models as $model)

self$ items[$type][$model-gtcode]=$model-gtname

Our new code mainly provides two static methods Lookupitems() and Lookupitem()The former returns a list of strings belonging to the specified data type while the latterreturns a particular string for the given data type and data value

Our blog database is pre-populated with two lookup types PostStatus and CommentStatusThe former refers to the possible post statuses while the latter the comment statuses

In order to make our code easier to read we also declare a set of constants to representthe status integer values We should use these constants through our code when referringto the corresponding status values

32 Creating and Updating Posts 23

class Post extends CActiveRecord

const STATUS DRAFT=0

const STATUS PUBLISHED=1

const STATUS ARCHIVED=2

Therefore we can call Lookupitems(rsquoPostStatusrsquo) to get the list of possible post statuses(text strings indexed by the corresponding integer values) and call Lookupitem(rsquoPostStatusrsquoPostSTATUS PUBLISHED) to get the string representation of the published status

32 Creating and Updating Posts

With the Post model ready we need to fine-tune the actions and views for the controllerPostController In this section we first customize the access control of CRUD operationswe then modify the code implementing the create and update operations

321 Customizing Access Control

The first thing we want to do is to customize the access control because the code generatedby yiic does not fit our needs

We modify the accessRules() method in the file wwwrootblogprotectedcontrollers

PostControllerphp as follows

public function accessRules()

return array(

array(rsquoallowrsquo allow all users to perform rsquolistrsquo and rsquoshowrsquo actions

rsquoactionsrsquo=gtarray(rsquoindexrsquo rsquoviewrsquo)

rsquousersrsquo=gtarray(rsquorsquo)

)

array(rsquoallowrsquo allow authenticated users to perform any action

rsquousersrsquo=gtarray(rsquorsquo)

)

array(rsquodenyrsquo deny all users

rsquousersrsquo=gtarray(rsquorsquo)

)

)

The above rules state that all users can access the index and view actions and authenti-cated users can access any actions including the admin action The user should be denied

24 3 Post Management

access in any other scenario Note that these rules are evaluated in the order they arelisted here The first rule matching the current context makes the access decision Forexample if the current user is the system owner who tries to visit the post creation pagethe second rule will match and it will give the access to the user

322 Customizing create and update Operations

The create and update operations are very similar They both need to display an HTMLform to collect user inputs validate them and save them into database The main differ-ence is that the update operation will pre-populate the form with the existing post datafound in the database For this reason the yiic tool generates a partial view wwwroot

blogprotectedviewspost formphp that is embedded in both the create and update

views to render the needed HTML form

We first change the formphp file so that the HTML form only collects the inputs wewant title content tags and status We use plain text fields to collect inputs for thefirst three attributes and a dropdown list to collect input for status The dropdown listoptions are the text displays of the possible post statuses

ltphp echo CHtmlactiveDropDownList($postrsquostatusrsquoLookupitems(rsquoPostStatusrsquo)) gt

In the above we call Lookupitems(rsquoPostStatusrsquo) to bring back the list of post statuses

We then modify the Post class so that it can automatically set some attributes (eg create

time author id) before a post is saved to the database We override the beforeSave()

method as follows

protected function beforeSave()

if(parentbeforeSave())

if($this-gtisNewRecord)

$this-gtcreate time=$this-gtupdate time=time()

$this-gtauthor id=Yiiapp()-gtuser-gtid

else

$this-gtupdate time=time()

return true

else

return false

33 Displaying Posts 25

When we save a post we want to update the tbl tag table to reflect the change of tagfrequencies We can do this work in the afterSave() method which is automaticallyinvoked by Yii after a post is successfully saved into the database

protected function afterSave()

parentafterSave()

Tagmodel()-gtupdateFrequency($this-gt oldTags $this-gttags)

private $ oldTags

protected function afterFind()

parentafterFind()

$this-gt oldTags=$this-gttags

In the implementation because we want to detect if the user changes the tags in case he isupdating an existing post we need to know what the old tags are For this reason we alsowrite the afterFind() method to keep the old tags in the variable oldTags The methodafterFind() is invoked automatically by Yii when an AR record is populated with thedata from database

We are not going to give details of the TagupdateFrequency() method here Interestedreaders may refer to the file wwwrootyiidemosblogprotectedmodelsTagphp

33 Displaying Posts

In our blog application a post may be displayed among a list of posts or by itself Theformer is implemented as the index operation while the latter the view operation In thissection we customize both operations to fulfill our initial requirements

331 Customizing view Operation

The view operation is implemented by the actionView() method in PostController Itsdisplay is generated by the view view with the view file wwwrootblogprotectedviews

postviewphp

Below is the relevant code implementing the view operation in PostController

public function actionView()

26 3 Post Management

$this-gtrender(rsquoviewrsquoarray(

$post=$this-gtloadModel()

$this-gtrender(rsquoviewrsquoarray(

rsquomodelrsquo=gt$post

))

))

private $ model

public function loadModel()

if($this-gt model===null)

if(isset($ GET[rsquoidrsquo]))

if(Yiiapp()-gtuser-gtisGuest)

$condition=rsquostatus=rsquoPostSTATUS PUBLISHED

rsquo OR status=rsquoPOSTSTATUS ARCHIVED

else

$condition=rsquorsquo

$this-gt model=Postmodel()-gtfindbyPk($ GET[rsquoidrsquo] $condition)

if($this-gt model===null)

throw new CHttpException(404rsquoThe requested page does not existrsquo)

return $this-gt model

Our change mainly lies in the loadModel() method In this method we query the Post

table according to the id GET parameter If the post is not found or if it is not publishedor archived (when the user is a guest) we will throw a 404 HTTP error Otherwise thepost object is returned to actionView() which in turn passes the post object to the viewscript for further display

Tip Yii captures HTTP exceptions (instances of CHttpException) and displaysthem in either predefined templates or customized error views The skeleton appli-cation generated by yiic already contains a customized error view in wwwroot

blogprotectedviewssiteerrorphp We can modify this file if we want tofurther customize the error display

The change in the view script is mainly about ajdusting the formatting and styles of thepost display We will not go into details here Interested readers may refer to wwwroot

blogprotectedviewspostviewphp

33 Displaying Posts 27

332 Customizing index Operation

Like the view operation we customize the index operation in two places the actionIndex()

method in PostController and the view file wwwrootblogprotectedviewspostindex

php We mainly need to add the support for displaying a list of posts that are associatedwith a specified tag

Below is the modified actionIndex() method in PostController

public function actionIndex()

$criteria=new CDbCriteria(array(

rsquoconditionrsquo=gtrsquostatus=rsquoPostSTATUS PUBLISHED

rsquoorderrsquo=gtrsquoupdate time DESCrsquo

rsquowithrsquo=gtrsquocommentCountrsquo

))

if(isset($ GET[rsquotagrsquo]))

$criteria-gtaddSearchCondition(rsquotagsrsquo$ GET[rsquotagrsquo])

$dataProvider=new CActiveDataProvider(rsquoPostrsquo array(

rsquopaginationrsquo=gtarray(

rsquopageSizersquo=gtYiiapp()-gtparams[rsquopostsPerPagersquo]

)

rsquocriteriarsquo=gt$criteria

))

$this-gtrender(rsquoindexrsquoarray(

rsquodataProviderrsquo=gt$dataProvider

))

In the above we first create a query criteria for retrieving post list The criteria statesthat only published posts should be returned and they should be sorted according to theirupdate time in descending order Because when displaying a post in the list we want toshow how many comments the post has received in the criteria we also specify to bringback commentCount which if you remember is a relation declared in Postrelations()

In case when a user wants to see posts with a specific tag we would add a search conditionto the criteria to look for the specified tag

Using the query criteria we create a data provider which mainly serves for three purposesFirst it does pagination of the data when too many results may be returned Herewe customize the pagination by setting the page size to be an application parameterpostsPerPage Second it does sorting according to the user request And finally it feedsthe paginated and sorted data to widgets or view code for presentation

28 3 Post Management

After we finish with actionIndex() we modify the index view as follows Our change ismainly about adding the h1 header when the user specifies to display posts with a tag

ltphp if(empty($ GET[rsquotagrsquo])) gt

lth1gtPosts Tagged with ltigtltphp echo CHtmlencode($ GET[rsquotagrsquo]) gtltigtlth1gt

ltphp endif gt

ltphp $this-gtwidget(rsquoziiwidgetsCListViewrsquo array(

rsquodataProviderrsquo=gt$dataProvider

rsquoitemViewrsquo=gtrsquo viewrsquo

rsquotemplatersquo=gtitemsnpager)) gt

Note that in the above we use [CListView] to display the post list This widget requires apartial view to display the detail of each individual post Here we specify the partial viewto be view which means the file wwwrootblogprotectedviewspost viewphp In thisview script we can acccess the post instance being displayed via a local variable named$data

34 Managing Posts

Managing posts mainly refers to listing posts in an administrative view that allows us tosee posts with all statuses updating them and deleting them They are accomplished bythe admin operation and the delete operation respectively The code generated by yiic

does not need much modification Below we mainly explain how these two operations areimplemented

341 Listing Posts in Tabular View

The admin operation shows posts with all statuses in a tabular view The view supportssorting and pagination The following is the actionAdmin() method in PostController

public function actionAdmin()

$dataProvider=new CActiveDataProvider(rsquoPostrsquo array(

rsquosortrsquo=gtarray(

rsquodefaultOrderrsquo=gtrsquostatus update time DESCrsquo

)

))

$this-gtrender(rsquoadminrsquoarray(

rsquodataProviderrsquo=gt$dataProvider

))

34 Managing Posts 29

The above code is very similar to that in actionIndex() The main difference is in thequery criteria Here we do not specify any particular query criteria since we want to seeall posts We do customize the sort property of the data provider so that by default theposts are shown in the order of their statuses and update time

Below is the code for the admin view

lth1gtManage Postslth1gt

ltphp $this-gtwidget(rsquoziiwidgetsgridCGridViewrsquo array(

rsquodataProviderrsquo=gt$dataProvider

rsquocolumnsrsquo=gtarray(

array(

rsquonamersquo=gtrsquotitlersquo

rsquotypersquo=gtrsquorawrsquo

rsquovaluersquo=gtrsquoCHtmllink(CHtmlencode($data-gttitle) $data-gturl)rsquo

)

array(

rsquonamersquo=gtrsquostatusrsquo

rsquovaluersquo=gtrsquoLookupitem(PostStatus$data-gtstatus)rsquo

)

rsquocreate timedatetimersquo

rsquoupdate timedatetimersquo

array(

rsquoclassrsquo=gtrsquoCButtonColumnrsquo

)

)

)) gt

We use [CGridView] to display the posts It allows us to sort by a column and paginatethrough the posts if there are too many to be displayed in a single page Our change ismainly about how to display each column For example for the title column we specifythat it should be displayed as a hyperlink that points to the detailed view of the post

Tip When displaying text we call CHtmlencode() to encode HTML entities init This prevents from cross-site scripting attack

342 Deleting Posts

In the admin data grid there is a delete button in each row Clicking on the button shoulddelete the corresponding post Internally this triggers the delete action implemented asfollows

30 3 Post Management

public function actionDelete()

if(Yiiapp()-gtrequest-gtisPostRequest)

we only allow deletion via POST request

$this-gtloadModel()-gtdelete()

if(isset($ POST[rsquoajaxrsquo]))

$this-gtredirect(array(rsquoindexrsquo))

else

throw new CHttpException(400rsquoInvalid request Please do not repeat this request againrsquo)

The above code is the one generated by the yiic tool without any change We wouldlike to explain a little bit more about the checking on $ POST[rsquoajaxrsquo] The [CGridView]widget has a very nice feature that its sorting pagination and deletion operations are alldone in AJAX mode by default That means the whole page does not get reloaded if anyof the above operations is performed However it is also possible that the widget runs innon-AJAX mode (by setting its ajaxUpdate property to be false or disabling JavaScript onthe client side) It is necessary for the delete action to differentiate these two scenarios ifthe delete request is made via AJAX we should not redirect the user browser otherwisewe should

Deleting a post should also cause the deletion of all comments for that post In additionwe should also update the tbl tag table regarding the tags for the deleted post Both ofthese tasks can be achieved by writing an afterDelete method in the Post model class asfollows

protected function afterDelete()

parentafterDelete()

Commentmodel()-gtdeleteAll(rsquopost id=rsquo$this-gtid)

Tagmodel()-gtupdateFrequency($this-gttags rsquorsquo)

The above code is very straightforward it first deletes all those comments whose post id

is the same as the ID of the deleted post it then updates the tbl tag table for the tags

of the deleted post

34 Managing Posts 31

Tip We have to explicitly delete all comments for the deleted post here becauseSQLite does not really support foreign key constraints In a DBMS that supportsthis constraint (such as MySQL PostgreSQL) the foreign key constraint can be setup such that the DBMS automatically deletes the related comments if the post isdeleted In that case we no longer this explicit deletion call in our code

32 3 Post Management

Chapter 4

Comment Management

41 Customizing Comment Model

For the Comment model we mainly need to customize the rules() and attributeLabels()

methods The attributeLabels() method returns a mapping between attribute namesand attribute labels We do not need to touch relations() since the code generated bythe yiic tool is good enough

411 Customizing rules() Method

We first customize the validation rules generated by the yiic tool The following rules areused for comments

public function rules()

return array(

array(rsquocontent author emailrsquo rsquorequiredrsquo)

array(rsquoauthor email urlrsquo rsquolengthrsquo rsquomaxrsquo=gt128)

array(rsquoemailrsquorsquoemailrsquo)

array(rsquourlrsquorsquourlrsquo)

)

In the above we specify that the author email and content attributes are required thelength of author email and url cannot exceed 128 the email attribute must be a validemail address and the url attribute must be a valid URL

412 Customizing attributeLabels() Method

We then customize the attributeLabels() method to declare the label display for eachmodel attribute This method returns an array consisting of name-label pairs When wecall CHtmlactiveLabel() to display an attribute label

34 4 Comment Management

public function attributeLabels()

return array(

rsquoidrsquo =gt rsquoIdrsquo

rsquocontentrsquo =gt rsquoCommentrsquo

rsquostatusrsquo =gt rsquoStatusrsquo

rsquocreate timersquo =gt rsquoCreate Timersquo

rsquoauthorrsquo =gt rsquoNamersquo

rsquoemailrsquo =gt rsquoEmailrsquo

rsquourlrsquo =gt rsquoWebsitersquo

rsquopost idrsquo =gt rsquoPostrsquo

)

Tip If the label for an attribute is not declared in attributeLabels() an algo-rithm will be used to generate an appropriate label For example a label VerifyCode will be generated for attributes verify code or verifyCode

413 Customizing Saving Process

Because we want to record the creation time of a comment we override the beforeSave()

method of Comment like we do for the Post model

protected function beforeSave()

if(parentbeforeSave())

if($this-gtisNewRecord)

$this-gtcreate time=time()

return true

else

return false

42 Creating and Displaying Comments

In this section we implement the comment display and creation features

421 Displaying Comments

Instead of displaying and creating comments on individual pages we use the post detailpage (generated by the view action of PostController) Below the post content display

42 Creating and Displaying Comments 35

we display first a list of comments belonging to that post and then a comment creationform

In order to display comments on the post detail page we modify the view script wwwrootblogprotectedviewspostviewphp as follows

ltdiv id=commentsgt

ltphp if($model-gtcommentCountgt=1) gt

lth3gt

ltphp echo $model-gtcommentCount rsquocomment(s)rsquo gt

lth3gt

ltphp $this-gtrenderPartial(rsquo commentsrsquoarray(

rsquopostrsquo=gt$model

rsquocommentsrsquo=gt$model-gtcomments

)) gt

ltphp endif gt

ltdivgt

In the above we call renderPartial() to render a partial view named comments to displaythe list of comments belonging to the current post Note that in the view we use theexpression $model-gtcomments to retrieve the comments for the post This is valid becausewe have declared a comments relation in the Post class Evaluating this expression wouldtrigger an implicit JOIN database query to bring back the proper comments This featureis known as lazy relational query

The partial view comments is not very interesting It mainly goes through every commentand displays the detail of it Interested readers may refer to wwwrootyiidemosblog

protectedpost commentsphp

422 Creating Comments

To handle comment creation we first modify the actionView() method of PostController

as follows

public function actionView()

$post=$this-gtloadModel()

$comment=$this-gtnewComment($post)

$this-gtrender(rsquoviewrsquoarray(

rsquomodelrsquo=gt$post

rsquocommentrsquo=gt$comment

))

36 4 Comment Management

protected function newComment($post)

$comment=new Comment

if(isset($ POST[rsquoCommentrsquo]))

$comment-gtattributes=$ POST[rsquoCommentrsquo]

if($post-gtaddComment($comment))

if($comment-gtstatus==CommentSTATUS PENDING)

Yiiapp()-gtuser-gtsetFlash(rsquocommentSubmittedrsquorsquoThank yoursquo)

$this-gtrefresh()

return $comment

In the above we call the newComment() method before we render view In the newComment()

method we generate a Comment instance and check if the comment form is submitted If sowe try to add the comment for the post by calling $post-gtaddComment($comment) If it goesthrough we refresh the post detail page In case the comment needs to be approved we willshow a flash message to indicate this decision A flash message is usually a confirmationmessage displayed to end users If the user clicks on the refresh button of his browser themessage will disappear

We also need to modify wwwrootblogprotectedviewspostviewphp furthermore

ltdiv id=commentsgt

lth3gtLeave a Commentlth3gt

ltphp if(Yiiapp()-gtuser-gthasFlash(rsquocommentSubmittedrsquo)) gt

ltdiv class=successgt

ltphp echo Yiiapp()-gtuser-gtgetFlash(rsquocommentSubmittedrsquo) gt

ltdivgt

ltphp else gt

ltphp $this-gtrenderPartial(rsquocomment formrsquoarray(

rsquomodelrsquo=gt$comment

)) gt

ltphp endif gt

ltdivgtlt-- comments --gt

43 Managing Comments 37

In the above code we display the flash message if it is available If not we displaythe comment input form by rendering the partial view wwwrootblogprotectedviews

comment formphp

43 Managing Comments

Comment management includes updating deleting and approving comments These op-erations are implemented as actions in the CommentController class

431 Updating and Deleting Comments

The code generated by yiic for updating and deleting comments remains largely un-changed

432 Approving Comments

When comments are newly created they are in pending approval status and need to beapproved in order to be visible to guest users Approving a comment is mainly aboutchanging the status column of the comment

We create an actionApprove() method in CommentController as follows

public function actionApprove()

if(Yiiapp()-gtrequest-gtisPostRequest)

$comment=$this-gtloadModel()

$comment-gtapprove()

$this-gtredirect(array(rsquoindexrsquo))

else

throw new CHttpException(400rsquoInvalid requestrsquo)

In the above when the approve action is invoked via a POST request we call the approve()

method defined in the Comment model to change the status We then redirect the userbrowser to the page displaying the post that this comment belongs to

We also modify the actionIndex() method of Comment to show all comments We wouldlike to see comments pending approved to show up first

public function actionIndex()

38 4 Comment Management

$dataProvider=new CActiveDataProvider(rsquoCommentrsquo array(

rsquocriteriarsquo=gtarray(

rsquowithrsquo=gtrsquopostrsquo

rsquoorderrsquo=gtrsquotstatus tcreate time DESCrsquo

)

))

$this-gtrender(rsquoindexrsquoarray(

rsquodataProviderrsquo=gt$dataProvider

))

Notice that in the above code because both tbl post and tbl comment have columns statusand create time we need to disambiguate the corresponding column reference by prefixingthem with table alias names As described in the guide the alias for the primary table ina relational query is always t Therefore we are prefixing t to the status and create time

columns in the above code

Like the post index view the index view for CommentController uses [CListView] to displaythe comment list which in turn uses the partial view wwwrootblogprotectedviews

comment viewphp to display the detail of each individual comment We will not go intodetails here Interested readers may refer to the corresponding file in the blog demowwwrootyiidemosblogprotectedviewscomment viewphp

Chapter 5

Portlets

51 Customizing Page Layout

Features like rdquothe most recent commentsrdquo rdquotag cloudrdquo are better to be implemented inportlets A portlet is a pluggable user interface component that renders a fragment ofHTML code In this section we describe how to set up the portlet architecture for ourblog application

Before we go into details about developing portlets we first need to adjust our page layoutso that the portlets can be placed in a side bar

A page layout determines how to share common sections (eg header footer) amongdifferent pages and how to embed contents that are specific for different pages In Yiia page layout is represented as a view under the protectedviewslayouts folder Whenwe call the render() method in a controller the rendering result will be automaticallyembedded into this layout view before it is displayed to end users The default layoutview is named as main which means the wwwrootblogprotectedviewslayoutsmain

php file in our blog application

We could directly modify the main layout to add a side bar However doing so wouldmake some irrelevant pages such as about contact pages to have a side bar which is notalways desirable For this reason we need different layouts for different pages

We thus create two different layouts column1 and column2 The former displays the pagein a single column while the latter displays two columns in the page one of which is theside bar We will use column2 layout for the post and comment pages and column1 forthe rest of the pages Because both column1 and column2 still share the same header andfooter sections we let them to share the main layout

Based on the above description we create the column1 layout as follows

ltphp $this-gtbeginContent(rsquolayoutsmainrsquo) gt

ltdiv class=containergt

40 5 Portlets

ltdiv id=contentgt

ltphp echo $content gt

ltdivgtlt-- content --gt

ltdivgt

ltphp $this-gtendContent() gt

and the column2 layout as follows

ltphp $this-gtbeginContent(rsquolayoutsmainrsquo) gt

ltdiv class=containergt

ltdiv class=span-18gt

ltdiv id=contentgt

ltphp echo $content gt

ltdivgtlt-- content --gt

ltdivgt

ltdiv class=span-6 lastgt

ltdiv id=sidebargt

portlets to be placed here

ltdivgtlt-- sidebar --gt

ltdivgt

ltdivgt

ltphp $this-gtendContent() gt

As we can see both column1 and column2 are enclosed in a pair of beginContent andendContent calls They essentially use the specified view layoutsmain to decorate theenclosed content In all these layout views the variable $content refers to the dynamiccontent to be embedded into the layout

Besides customizing the layout view file we also need to adjust the CSS file wwwroot

blogcssmaincss so that the overall appearance would look like what we see in the blogdemo We will not go into details here

Info The skeleton application generated by the yiic tool uses the BluePrint CSSframework It provides a nice font reset and a robust grid system For more detailson how to use this framework please refer to its documentation

After creating the new layouts we need to set the layout property of controllers to makeuse of them So far we have three controllers SiteController PostController andCommentController We set SiteController to use column1 as the default layout whilethe rest two of the controllers use column2 for example

class PostController extends Controller

52 Creating User Menu Portlet 41

public $layout=rsquocolumn2rsquo

Tip If some view of the controller does not use the same layout as declaredby the layout property it may customize it in the view by a simple assignment$this-gtlayout=rsquonewlayoutrsquo

52 Creating User Menu Portlet

Based on the requirements analysis we need three different portlets the rdquouser menurdquoportlet the rdquotag cloudrdquo portlet and the rdquorecent commentsrdquo portlet We will develop theseportlets by extending the [CPortlet] widget provided by Yii

In this section we will develop our first concrete portlet - the user menu portlet whichdisplays a list of menu items that are only available to authenticated users The menucontains four items

bull Approve Comments a hyperlink that leads to a list of comments pending approval

bull Create New Post a hyperlink that leads to the post creation page

bull Manage Posts a hyperlink that leads to the post management page

bull Logout a link button that would log out the current user

521 Creating UserMenu Class

We create the UserMenu class to represent the logic part of the user menu portlet Theclass is saved in the file wwwrootblogprotectedcomponentsUserMenuphp which has thefollowing content

Yiiimport(rsquoziiwidgetsCPortletrsquo)

class UserMenu extends CPortlet

public function init()

$this-gttitle=CHtmlencode(Yiiapp()-gtuser-gtname)

parentinit()

42 5 Portlets

protected function renderContent()

$this-gtrender(rsquouserMenursquo)

The UserMenu class extends from the CPortlet class that we created previously It overridesboth the init() method and the renderContent() method of CPortlet The former setsthe portlet title to be the name of the current user the latter generates the portlet bodycontent by rendering a view named userMenu

Tip Notice that we have to explicitly include the CPortlet class by calling Yii

import() before we refer to it the first time This is because CPortlet is part of thezii project ndash the official extension library for Yii For performance considerationclasses in this project are not listed as core classes Therefore we have to import itbefore we use it the first time

522 Creating userMenu View

Next we create the userMenu view which is saved in the file wwwrootblogprotected

componentsviewsuserMenuphp

ltulgt

ltligtltphp echo CHtmllink(rsquoCreate New Postrsquoarray(rsquopostcreatersquo)) gtltligt

ltligtltphp echo CHtmllink(rsquoManage Postsrsquoarray(rsquopostadminrsquo)) gtltligt

ltligtltphp echo CHtmllink(rsquoApprove Commentsrsquoarray(rsquocommentindexrsquo))

rsquo (rsquo Commentmodel()-gtpendingCommentCount rsquo)rsquo gtltligt

ltligtltphp echo CHtmllink(rsquoLogoutrsquoarray(rsquositelogoutrsquo)) gtltligt

ltulgt

Info By default view files for a widget should be placed under the views sub-directory of the directory containing the widget class file The file name must bethe same as the view name

523 Using UserMenu Portlet

It is time for us to make use of our newly completed UserMenu portlet We modify thelayout view file wwwrootblogprotectedviewslayoutscolumn2php as follows

53 Creating Tag Cloud Portlet 43

ltdiv id=sidebargt

ltphp if(Yiiapp()-gtuser-gtisGuest) $this-gtwidget(rsquoUserMenursquo) gt

ltdivgt

In the above we call the widget() method to generate and execute an instance of theUserMenu class Because the portlet should only be displayed to authenticated users weonly call widget() when the isGuest property of the current user is false (meaning theuser is authenticated)

524 Testing UserMenu Portlet

Letrsquos test what we have so far

1 Open a browser window and enter the URL httpwwwexamplecomblogindex

php Verify that there is nothing displayed in the side bar section of the page

2 Click on the Login hyperlink and fill out the login form to login If successful verifythat the UserMenu portlet appears in the side bar and the portlet has the usernameas its title

3 Click on the rsquoLogoutrsquo hyperlink in the UserMenu portlet Verify that the logout actionis successful and the UserMenu portlet disappears

525 Summary

What we have created is a portlet that is highly reusable We can easily reuse it in adifferent project with little or no modification Moreover the design of this portlet followsclosely the philosophy that logic and presentation should be separated While we did notpoint this out in the previous sections such practice is used nearly everywhere in a typicalYii application

53 Creating Tag Cloud Portlet

Tag cloud displays a list of post tags with visual decorations hinting the popularity of eachindividual tag

531 Creating TagCloud Class

We create the TagCloud class in the file wwwrootblogprotectedcomponentsTagCloud

php The file has the following content

44 5 Portlets

Yiiimport(rsquoziiwidgetsCPortletrsquo)

class TagCloud extends CPortlet

public $title=rsquoTagsrsquo

public $maxTags=20

protected function renderContent()

$tags=Tagmodel()-gtfindTagWeights($this-gtmaxTags)

foreach($tags as $tag=gt$weight)

$link=CHtmllink(CHtmlencode($tag) array(rsquopostindexrsquorsquotagrsquo=gt$tag))

echo CHtmltag(rsquospanrsquo array(

rsquoclassrsquo=gtrsquotagrsquo

rsquostylersquo=gtfont-size$weightpt) $link)n

Unlike the UserMenu portlet the TagCloud portlet does not use a view Instead its presen-tation is done in the renderContent() method This is because the presentation does notcontain much HTML tags

We display each tag as a hyperlink to the post index page with the corresponding tagparameter The font size of each tag link is adjusted according to their relative weightamong other tags If a tag has higher frequency value than the other it will have a biggerfont size

532 Using TagCloud Portlet

Usage of the TagCloud portlet is very simple We modify the layout file wwwrootblog

protectedviewslayoutsmainphp as follows

ltdiv id=sidebargt

ltphp if(Yiiapp()-gtuser-gtisGuest) $this-gtwidget(rsquoUserMenursquo) gt

ltphp $this-gtwidget(rsquoTagCloudrsquo array(

rsquomaxTagsrsquo=gtYiiapp()-gtparams[rsquotagCloudCountrsquo]

)) gt

ltdivgt

54 Creating Recent Comments Portlet 45

54 Creating Recent Comments Portlet

In this section we create the last portlet that displays a list of comments recently pub-lished

541 Creating RecentComments Class

We create the RecentComments class in the file wwwrootblogprotectedcomponentsRecentCommentsphp The file has the following content

Yiiimport(rsquoziiwidgetsCPortletrsquo)

class RecentComments extends CPortlet

public $title=rsquoRecent Commentsrsquo

public $maxComments=10

public function getRecentComments()

return Commentmodel()-gtfindRecentComments($this-gtmaxComments)

protected function renderContent()

$this-gtrender(rsquorecentCommentsrsquo)

In the above we invoke the findRecentComments method which is defined in the Comment

class as follows

class Comment extends CActiveRecord

public function findRecentComments($limit=10)

return $this-gtwith(rsquopostrsquo)-gtfindAll(array(

rsquoconditionrsquo=gtrsquotstatus=rsquoselfSTATUS APPROVED

rsquoorderrsquo=gtrsquotcreate time DESCrsquo

rsquolimitrsquo=gt$limit

))

46 5 Portlets

542 Creating recentComments View

The recentComments view is saved in the file wwwrootblogprotectedcomponentsviews

recentCommentsphp It simply displays every comment returned by the RecentComments

getRecentComments() method

543 Using RecentComments Portlet

We modify the layout file wwwrootblogprotectedviewslayoutsmainphp to embedthis last portlet

ltdiv id=sidebargt

ltphp if(Yiiapp()-gtuser-gtisGuest) $this-gtwidget(rsquoUserMenursquo) gt

ltphp $this-gtwidget(rsquoTagCloudrsquo array(

rsquomaxTagsrsquo=gtYiiapp()-gtparams[rsquotagCloudCountrsquo]

)) gt

ltphp $this-gtwidget(rsquoRecentCommentsrsquo array(

rsquomaxCommentsrsquo=gtYiiapp()-gtparams[rsquorecentCommentCountrsquo]

)) gt

ltdivgt

Chapter 6

Final Work

61 Beautifying URLs

The URLs linking various pages of our blog application currently look ugly For examplethe URL for the page showing a post looks like the following

indexphpr=postshowampid=1

In this section we describe how to beautify these URLs and make them SEO-friendlyOur goal is to be able to use the following URLs in the application

1 indexphppostsyii leads to the page showing a list of posts with tag yii

2 indexphppost2A+Test+Post leads to the page showing the detail of the postwith ID 2 whose title is A Test Post

3 indexphppostupdate1 leads to the page that allows updating the post with ID1

Note that in the second URL format we include the post title in the URL This is mainlyto make the URL SEO friendly It is said that search engines may also respect the wordsfound in a URL when it is being indexed

To achieve our goal we modify the application configuration as follows

return array(

rsquocomponentsrsquo=gtarray(

rsquourlManagerrsquo=gtarray(

rsquourlFormatrsquo=gtrsquopathrsquo

rsquorulesrsquo=gtarray(

48 6 Final Work

rsquopostltidd+gtlttitlegtrsquo=gtrsquopostviewrsquorsquopostslttaggtrsquo=gtrsquopostindexrsquo

)

)

)

)

In the above we configure the urlManager component by setting its urlFormat propertyto be path and adding a set of rules

The rules are used by urlManager to parse and create the URLs in the desired formatFor example the first rule says that if a URL indexphppostsyii is requested theurlManager component should be responsible to dispatch the request to the route post

index and generate a tag GET parameter with the value yii On the other hand whencreating a URL with the route postindex and parameter tag the urlManager componentwill also use this rule to generate the desired URL indexphppostsyii For this reasonwe say that urlManager is a two-way URL manager

The urlManager component can further beautify our URLs such as hiding indexphp inthe URLs appending suffix like html to the URLs We can obtain these features easilyby configuring various properties of urlManager in the application configuration For moredetails please refer to the Guide

62 Logging Errors

A production Web application often needs sophisticated logging for various events In ourblog application we would like to log the errors occurring when it is being used Sucherrors could be programming mistakes or usersrsquo misuse of the system Logging these errorswill help us to improve the blog application

We enable the error logging by modifying the application configuration as follows

return array(

rsquopreloadrsquo=gtarray(rsquologrsquo)

rsquocomponentsrsquo=gtarray(

rsquologrsquo=gtarray(

rsquoclassrsquo=gtrsquoCLogRouterrsquo

rsquoroutesrsquo=gtarray(

array(

rsquoclassrsquo=gtrsquoCFileLogRoutersquo

rsquolevelsrsquo=gtrsquoerror warningrsquo

63 Final Tune-up and Deployment 49

)

)

)

)

)

With the above configuration if an error or warning occurs detailed information will belogged and saved in a file located under the directory wwwrootblogprotectedruntime

The log component offers more advanced features such as sending log messages to a listof email addresses displaying log messages in JavaScript console window etc For moredetails please refer to the Guide

63 Final Tune-up and Deployment

We are close to finish our blog application Before deployment we would like to do sometune-ups

631 Changing Home Page

We change to use the post list page as the home page We modify the application config-uration as follows

return array(

rsquodefaultControllerrsquo=gtrsquopostrsquo

)

Tip Because PostController already declares list to be its default action whenwe access the home page of the application we will see the result generated by thelist action of the post controller

632 Enabling Schema Caching

Because ActiveRecord relies on the metadata about tables to determine the column infor-mation it takes time to read the metadata and analyze it This may not be a problemduring development stage but for an application running in production mode it is a totalwaste of time if the database schema does not change Therefore we should enable theschema caching by modifying the application configuration as follows

50 6 Final Work

return array(

rsquocomponentsrsquo=gtarray(

rsquocachersquo=gtarray(

rsquoclassrsquo=gtrsquoCDbCachersquo

)

rsquodbrsquo=gtarray(

rsquoclassrsquo=gtrsquosystemdbCDbConnectionrsquo

rsquoconnectionStringrsquo=gtrsquosqlitewwwrootblogprotecteddatablogdbrsquo

rsquoschemaCachingDurationrsquo=gt3600

)

)

)

In the above we first add a cache component which uses a default SQLite database as thecaching storage If our server is equipped with other caching extensions such as APCwe could change to use them as well We also modify the db component by setting itsschemaCachingDuration property to be 3600 which means the parsed database schemadata can remain valid in cache for 3600 seconds

633 Disabling Debugging Mode

We modify the entry script file wwwrootblogindexphp by removing the line definingthe constant YII DEBUG This constant is useful during development stage because it allowsYii to display more debugging information when an error occurs However when theapplication is running in production mode displaying debugging information is not agood idea because it may contain sensitive information such as where the script file islocated and the content in the file etc

634 Deploying the Application

The final deployment process manly involves copying the directory wwwrootblog to thetarget directory The following checklist shows every needed step

1 Install Yii in the target place if it is not available

2 Copy the entire directory wwwrootblog to the target place

3 Edit the entry script file indexphp by pointing the $yii variable to the new Yiibootstrap file

4 Edit the file protectedyiicphp by setting the $yiic variable to be the new Yiiyiicphp file

64 Future Enhancements 51

5 Change the permission of the directories assets and protectedruntime so that theyare writable by the Web server process

64 Future Enhancements

641 Using a Theme

Without writing any code our blog application is already themeable To use a themewe mainly need to develop the theme by writing customized view files in the theme Forexample to use a theme named classic that uses a different page layout we would createa layout view file wwwrootblogthemesclassicviewslayoutsmainphp We also needto change the application configuration to indicate our choice of the classic theme

return array(

rsquothemersquo=gtrsquoclassicrsquo

)

642 Internationalization

We may also internationalize our blog application so that its pages can be displayed indifferent languages This mainly involves efforts in two aspects

First we may create view files in different languages For example for the list pageof PostController we can create a view file wwwrootblogprotectedviewspostzh cn

listphp When the application is configured to use simplified Chinese (the language codeis zh cn) Yii will automatically use this new view file instead of the original one

Second we may create message translations for those messages generated by code Themessage translations should be saved as files under the directory wwwrootblogprotected

messages We also need to modify the code where we use text strings by enclosing themin the method call Yiit()

For more details about internationalization please refer to the Guide

643 Improving Performance with Cache

While the Yii framework itself is very efficient it is not necessarily true that an applicationwritten in Yii efficient There are several places in our blog application that we can improvethe performance For example the tag clould portlet could be one of the performancebottlenecks because it involves complex database query and PHP logic

52 6 Final Work

We can make use of the sophisticated caching feature provided by Yii to improve theperformance One of the most useful components in Yii is COutputCache which caches afragment of page display so that the underlying code generating the fragment does not needto be executed for every request For example in the layout file wwwrootblogprotected

viewslayoutsmainphp we can enclose the tag cloud portlet with COutputCache

ltphp if($this-gtbeginCache(rsquotagCloudrsquo array(rsquodurationrsquo=gt3600))) gt

ltphp $this-gtwidget(rsquoTagCloudrsquo array(

rsquomaxTagsrsquo=gtYiiapp()-gtparams[rsquotagCloudCountrsquo]

)) gt

ltphp $this-gtendCache() gt

With the above code the tag cloud display will be served from cache instead of beinggenerated on-the-fly for every request The cached content will remain valid in cache for3600 seconds

644 Adding New Features

Our blog application only has very basic functionalities To become a complete blogsystem more features are needed for example calendar portlet email notifications postcategorization archived post portlet and so on We will leave the implementation of thesefeatures to interested readers

  • Contents
  • License
  • Getting Started
    • Building a Blog System using Yii
    • Testdriving with Yii
      • Installing Yii
      • Creating Skeleton Application
      • Application Workflow
        • Requirements Analysis
        • Overall Design
          • Initial Prototyping
            • Setting Up Database
              • Creating Database
              • Establishing Database Connection
                • Scaffolding
                • Authenticating User
                • Summary
                  • Post Management
                    • Customizing Post Model
                      • Customizing rules() Method
                      • Customizing relations() Method
                      • Representing Status in Text
                        • Creating and Updating Posts
                          • Customizing Access Control
                          • Customizing create and update Operations
                            • Displaying Posts
                              • Customizing view Operation
                              • Customizing index Operation
                                • Managing Posts
                                  • Listing Posts in Tabular View
                                  • Deleting Posts
                                      • Comment Management
                                        • Customizing Comment Model
                                          • Customizing rules() Method
                                          • Customizing attributeLabels() Method
                                          • Customizing Saving Process
                                            • Creating and Displaying Comments
                                              • Displaying Comments
                                              • Creating Comments
                                                • Managing Comments
                                                  • Updating and Deleting Comments
                                                  • Approving Comments
                                                      • Portlets
                                                        • Customizing Page Layout
                                                        • Creating User Menu Portlet
                                                          • Creating UserMenu Class
                                                          • Creating userMenu View
                                                          • Using UserMenu Portlet
                                                          • Testing UserMenu Portlet
                                                          • Summary
                                                            • Creating Tag Cloud Portlet
                                                              • Creating TagCloud Class
                                                              • Using TagCloud Portlet
                                                                • Creating Recent Comments Portlet
                                                                  • Creating RecentComments Class
                                                                  • Creating recentComments View
                                                                  • Using RecentComments Portlet
                                                                      • Final Work
                                                                        • Beautifying URLs
                                                                        • Logging Errors
                                                                        • Final Tune-up and Deployment
                                                                          • Changing Home Page
                                                                          • Enabling Schema Caching
                                                                          • Disabling Debugging Mode
                                                                          • Deploying the Application
                                                                            • Future Enhancements
                                                                              • Using a Theme
                                                                              • Internationalization
                                                                              • Improving Performance with Cache
                                                                              • Adding New Features
Page 7: Yii Blog 1.1.0 Demo Tutor

vi Contents

Chapter 1

Getting Started

11 Building a Blog System using Yii

This tutorial describes how to use Yii to develop a blog application shown as the blog demowhich can be found in the Yii release files It explains in detail every step to be takenduring the development which may also be applied in developing other Web applicationsAs a complement to the Guide and the Class Reference of Yii this tutorial aims to showpractical usage of Yii instead of thorough and definitive description

Readers of this tutorial are not required to have prior knowledge about Yii However basicknowledge of object-oriented programming (OOP) and database programming would helpreaders to understand the tutorial more easily

This tutorial is released under the Terms of Yii Documentation

12 Testdriving with Yii

In this section we describe how to create a skeleton application that will serve as ourstarting point For simplicity we assume that the document root of our Web server iswwwroot and the corresponding URL is httpwwwexamplecom

121 Installing Yii

We first install the Yii framework Grab a copy of the Yii release file (version 110 orabove) from wwwyiiframeworkcom and unpack it to the directory wwwrootyii Doublecheck to make sure that there is a directory wwwrootyiiframework

Tip The Yii framework can be installed anywhere in the file system not necessarilyunder a Web folder Its framework directory contains all framework code and isthe only framework directory needed when deploying an Yii application A singleinstallation of Yii can be used by multiple Yii applications

2 1 Getting Started

After installing Yii open a browser window and access the URL httpwwwexample

comyiirequirementsindexphp It shows the requirement checker provided in the Yiirelease For our blog application besides the minimal requirements needed by Yii we alsoneed to enable both the pdo and pdo sqlite PHP extensions so that we can access SQLitedatabases

122 Creating Skeleton Application

We then use the yiic tool to create a skeleton application under the directory wwwroot

blog The yiic tool is a command line tool provided in the Yii release It can be used togenerate code to reduce certain repetitive coding tasks

Open a command window and execute the following command

wwwrootyiiframeworkyiic webapp wwwrootblog

Create a Web application under rsquowwwrootblogrsquo [Yes|No]y

Tip In order to use the yiic tool as shown above the CLI PHP program must beon the command search path If not the following command may be used instead

pathtophp wwwrootyiiframeworkyiicphp webapp wwwrootblog

To try out the application we just created open a Web browser and navigate to the URLhttpwwwexamplecomblogindexphp We should see that our skeleton applicationalready has four fully functional pages the homepage the about page the contact pageand the login page

In the following we briefly describe what we have in this skeleton application

Entry Script

We have an entry script file wwwrootblogindexphp which has the following content

ltphp

$yii=rsquowwwrootframeworkyiiphprsquo

$config=dirname( FILE )rsquoprotectedconfigmainphprsquo

remove the following line when in production mode

defined(rsquoYII DEBUGrsquo) or define(rsquoYII DEBUGrsquotrue)

12 Testdriving with Yii 3

require once($yii)

YiicreateWebApplication($config)-gtrun()

This is the only script that Web users can directly access The script first includes theYii bootstrap file yiiphp It then creates an application instance with the specifiedconfiguration and executes the application

Base Application Directory

We also have an application base directory wwwrootblogprotected The majority of ourcode and data will be placed under this directory and it should be protected from beingaccessed by Web users For Apache httpd Web server we place under this directory ahtaccess file with the following content

deny from all

For other Web servers please refer to the corresponding manual on how to protect adirectory from being accessed by Web users

123 Application Workflow

To help understand how Yii works we describe the main workflow in our skeleton appli-cation when a user is accessing its contact page

1 The user requests the URL httpwwwexamplecomblogindexphpr=sitecontact

2 The entry script is executed by the Web server to process the request

3 An application instance is created and configured with initial property values speci-fied in the application configuration file wwwrootblogprotectedconfigmainphp

4 The application resolves the request into a controller and a controller action For thecontact page request it is resolved as the site controller and the contact action (theactionContact method in wwwrootblogprotectedcontrollersSiteControllerphp)

5 The application creates the site controller in terms of a SiteController instanceand then executes it

6 The SiteController instance executes the contact action by calling its actionContact()method

4 1 Getting Started

7 The actionContact method renders a view named contact to the Web user Inter-nally this is achieved by including the view file wwwrootblogprotectedviews

sitecontactphp and embedding the result into the layout file wwwrootblog

protectedviewslayoutsmainphp

13 Requirements Analysis

The blog system that we are going to develop is a single user system The owner of thesystem will be able to perform the following actions

bull Login and logout

bull Create update and delete posts

bull Publish unpublish and archive posts

bull Approve and delete comments

All other users are guest users who can perform the following actions

bull Read posts

bull Create comments

Additional Requirements for this system include

bull The homepage of the system should display a list of the most recent posts

bull If a page contains more than 10 posts they should be displayed in pages

bull The system should display a post together with its comments

bull The system should be able to list posts with a specified tag

bull The system should show a cloud of tags indicating their use frequencies

bull The system should show a list of most recent comments

bull The system should be themeable

bull The system should use SEO-friendly URLs

14 Overall Design 5

14 Overall Design

Based on the analysis of the requirements we decide to use the following database tablesto store the persistent data for our blog application

bull tbl user stores the user information including username and password

bull tbl post stores the blog post information It mainly consists of the following columns

ndash title required title of the post

ndash content required body content of the post which uses the Markdown format

ndash status required status of the post which can be one of following values

lowast 1 meaning the post is in draft and is not visible to public

lowast 2 meaning the post is published to public

lowast 3 meaning the post is outdated and is not visible in the post list (stillaccessible individually though)

ndash tags optional a list of comma-separated words categorizing the post

bull tbl comment stores the post comment information Each comment is associated witha post and mainly consists of the following columns

ndash name required the author name

ndash email required the author email

ndash website optional the author website URL

ndash content required the comment content in plain text format

ndash status required status of the comment which indicates whether the commentis approved (value 2) or not (value 1)

bull tbl tag stores post tag frequency information that is needed to implement the tagcloud feature The table mainly contains the following columns

ndash name required the unique tag name

ndash frequency required the number of times that the tag appears in posts

bull tbl lookup stores generic lookup information It is essentially a map between integervalues and text strings The former is the data representation in our code while thelatter is the corresponding presentation to end users For example we use integer1 to represent the draft post status and string Draft to display this status to endusers This table mainly contains the following columns

6 1 Getting Started

ndash name the textual representation of the data item that is to be displayed to endusers

ndash code the integer representation of the data item

ndash type the type of the data item

ndash position the relative display order of the data item among other items of thesame type

The following entity-relation (ER) diagram shows the table structure and relationshipsabout the above tables

Figure 11 Entity-Relation Diagram of the Blog Database

Complete SQL statements corresponding to the above ER diagram may be found inthe blog demo In our Yii installation they are in the file wwwrootyiidemosblog

protecteddataschemasqlitesql

Info We name all our table names and column names in lower case This is becausedifferent DBMS often have different case-sensitivity treatment and we want to avoidtroubles like this

We also prefix all our tables with tbl This serves for two purposes First the prefixintroduces a namespace to these tables in case when they need to coexist with othertables in the same database which often happens in a shared hosting environmentwhere a single database is being used by multiple applications Second using tableprefix reduces the possibility of having some table names that are reserved keywordsin DBMS

14 Overall Design 7

We divide the development of our blog application into the following milestones

bull Milestone 1 creating a prototype of the blog system It should consist of most ofthe required functionalities

bull Milestone 2 completing post management It includes creating listing showingupdating and deleting posts

bull Milestone 3 completing comment management It includes creating listing ap-proving updating and deleting post comments

bull Milestone 4 implementing portlets It includes user menu login tag cloud andrecent comments portlets

bull Milestone 5 final tune-up and deployment

8 1 Getting Started

Chapter 2

Initial Prototyping

21 Setting Up Database

Having created a skeleton application and finished the database design in this section wewill create the blog database and establish the connection to it in the skeleton application

211 Creating Database

We choose to create a SQLite database Because the database support in Yii is built on topof PDO we can easily switch to use a different type of DBMS (eg MySQL PostgreSQL)without the need to change our application code

We create the database file blogdb under the directory wwwrootblogprotecteddataNote that both the directory and the database file have to be writable by the Web serverprocess as required by SQLite We may simply copy the database file from the blogdemo in our Yii installation which is located at wwwrootyiidemosblogprotecteddatablogdb We may also generate the database by executing the SQL statements in the filewwwrootyiidemosblogprotecteddataschemasqlitesql

Tip To execute SQL statements we may use the sqlite3 command line tool thatcan be found in the SQLite official website

212 Establishing Database Connection

To use the blog database in the skeleton application we created we need to modify itsapplication configuration which is stored in the PHP script wwwrootblogprotected

configmainphp The script returns an associative array consisting of name-value pairseach of which is used to initialize a writable property of the application instance

We configure the db component as follows

10 2 Initial Prototyping

return array(

rsquocomponentsrsquo=gtarray(

rsquodbrsquo=gtarray(

rsquoconnectionStringrsquo=gtrsquosqlitewwwrootblogprotecteddatablogdbrsquo

rsquotablePrefixrsquo=gtrsquotbl rsquo

)

)

)

The above configuration says that we have a db application component whose connectionStringproperty should be initialized as sqlitewwwrootblogprotecteddatablogdb and whosetablePrefix property should be tbl

With this configuration we can access the DB connection object using Yiiapp()-gtdb

at any place in our code Note that Yiiapp() returns the application instance that wecreate in the entry script If you are interested in possible methods and properties that theDB connection has you may refer to its class reference However in most cases we are notgoing to use this DB connection directly Instead we will use the so-called ActiveRecordto access the database

We would like to explain a bit more about the tablePrefix property that we set in theconfiguration This tells the db connection that it should respect the fact we are usingtbl as the prefix to our database table names In particular if in a SQL statement thereis a token enclosed within double curly brackets (eg post then the db connectionshould translate it into a name with the table prefix (eg tbl post) before sending it toDBMS for execution This feature is especially useful if in future we need to modify thetable name prefix without touching our source code For example if we are developing ageneric content management system (CMS) we may exploit this feature so that when itis being installed in a new environment we can allow users to choose a table prefix theylike

22 Scaffolding 11

Tip If you want to use MySQL instead of SQLite to store data you may cre-ate a MySQL database named blog using the SQL statements in wwwrootyii

demosblogprotecteddataschemamysqlsql Then modify the applicationconfiguration as follows

return array(

rsquocomponentsrsquo=gtarray(

rsquodbrsquo=gtarray(

rsquoconnectionStringrsquo =gt rsquomysqlhost=localhostdbname=blogrsquo

rsquoemulatePreparersquo =gt true

rsquousernamersquo =gt rsquorootrsquo

rsquopasswordrsquo =gt rsquorsquo

rsquocharsetrsquo =gt rsquoutf8rsquo

rsquotablePrefixrsquo =gt rsquotbl rsquo

)

)

)

22 Scaffolding

Create read update and delete (CRUD) are the four basic operations of data objects in anapplication Because the task of implementing the CRUD operations is so common whendeveloping Web applications Yii provides a tool to automate this process (also known asscaffolding) In this section we will describe how to use the tool to implement CRUD forposts and comments

Open a command window and run the following commands

wwwrootyiiframeworkyiic shell wwwrootblogindexphp

Yii Interactive Tool v11

Please type rsquohelprsquo for help Type rsquoexitrsquo to quit

gtgt model

gtgt crud Post

gtgt crud Comment

gtgt exit

12 2 Initial Prototyping

Info Some PHP installations may use a different phpini file for command line(CLI) PHP parser As a result when running the above yiic commands youmay encounter errors like rdquoYiiBaseinclude(PDOphp) failed to open streamrdquoor rdquocould not find driverrdquo Please double check your CLI PHP configuration byexecuting the following command

php -r phpinfo()

The result of the above command will show which phpini file is being used andwhich extensions are loaded If a wrong phpini file is used you may use thefollowing command to explicitly specify the correct phpini to use

php -c phpini wwwrootyiiframeworkyiicphp shell wwwrootblogindexphp

The commands above accomplish two tasks First the model command generates a modelclass file for each table in the blog database Second the crud commands generate thecode needed by the CRUD operations for the Post and Comment models

Tip The command model generates a model class for every table in the databaseSometimes we probably donrsquot want to do so (eg the database contains some irrel-evant tables) In this case we can create model classes one by one For exampleto create the User model we can use the command model User The model com-mand also has some more advanced usages For more details execute help model

command

We can test the generated code by accessing the following URLs

httpwwwexamplecomblogindexphpr=post

httpwwwexamplecomblogindexphpr=comment

Notice that the post and comment features implemented by the generated code are com-pletely independent of each other Also when creating a new post or comment we arerequired to enter information such as author id and create time which in real applica-tion should be set by the program Donrsquot worry We will fix these problems in the nextmilestones For now we should be fairly satisfied as this prototype already contains mostfeatures that we need to implement for the blog application

To prepare for the next milestones letrsquos take a closer look at the files generated by theabove commands All the files are generated under wwwrootblogprotected For conve-nience we group them into model files controller files and view files

22 Scaffolding 13

bull model files

ndash modelsUserphp contains the User class that extends from CActiveRecord andcan be used to access the tbl user database table

ndash modelsPostphp contains the Post class that extends from CActiveRecord andcan be used to access the tbl post database table

ndash modelsTagphp contains the Tag class that extends from CActiveRecord andcan be used to access the tbl tag database table

ndash modelsCommentphp contains the Comment class that extends from CActiveRe-cord and can be used to access the tbl comment database table

ndash modelsLookupphp contains the Lookup class that extends from CActiveRecordand can be used to access the tbl lookup database table

bull controller file

ndash controllersPostControllerphp contains the PostController class which is thecontroller in charge of all CRUD operations about posts

ndash controllersCommentControllerphp contains the CommentController class whichis the controller in charge of all CRUD operations about comments

bull view files

ndash viewspostcreatephp is the view file that shows an HTML form to create anew post

ndash viewspostupdatephp is the view file that shows an HTML form to update anexisting post

ndash viewspostviewphp is the view file that displays the detailed information of apost

ndash viewspostindexphp is the view file that displays a list of posts

ndash viewspostadminphp is the view file that displays posts in a table with ad-ministrative commands

ndash viewspost formphp is the partial view file embedded in viewspostcreate

php and viewspostupdatephp It displays the HTML form for collecting postinformation

ndash viewspost viewphp is the partial view file used by viewspostindexphp Itdisplays the brief view of a single post

ndash a similar set of view files are also generated for comment

14 2 Initial Prototyping

In order to understand better how the above files are used we show in the following theworkflow that occurs in the blog application when displaying a list of posts

1 The user requests the URL httpwwwexamplecomblogindexphpr=posts

2 The entry script is executed by the Web server which creates and initializes anapplication instance to handle the request

3 The application creates an instance of PostController and executes it

4 The PostController instance executes the index action by calling its actionIndex()

method Note that index is the default action if the user does not specify an actionto execute in the URL

5 The actionIndex() method queries database to bring back the list of recent posts

6 The actionIndex() method renders the index view with the post data

23 Authenticating User

Our blog application needs to differentiate between the system owner and guest usersTherefore we need to implement the user authentication feature

As you may have found that the skeleton application already provides user authenticationby checking if the username and password are both demo or admin In this section wewill modify the corresponding code so that the authentication is done against the User

database table

User authentication is performed in a class implementing the IUserIdentity interface Theskeleton application uses the UserIdentity class for this purpose The class is stored inthe file wwwrootblogprotectedcomponentsUserIdentityphp

Tip By convention the name of a class file must be the same as the correspondingclass name suffixed with the extension php Following this convention one canrefer to a class using a path alias For example we can refer to the UserIdentity

class with the alias applicationcomponentsUserIdentity Many APIs in Yiican recognize path aliases (eg YiicreateComponent()) and using path aliasesavoids the necessity of embedding absolute file paths in the code The existence ofthe latter often causes trouble when we deploy an application

We modify the UserIdentity class as follows

23 Authenticating User 15

ltphp

class UserIdentity extends CUserIdentity

private $ id

public function authenticate()

$username=strtolower($this-gtusername)

$user=Usermodel()-gtfind(rsquoLOWER(username)=rsquoarray($username))

if($user===null)

$this-gterrorCode=selfERROR USERNAME INVALID

else if($user-gtvalidatePassword($this-gtpassword))

$this-gterrorCode=selfERROR PASSWORD INVALID

else

$this-gt id=$user-gtid

$this-gtusername=$user-gtusername

$this-gterrorCode=selfERROR NONE

return $this-gterrorCode==selfERROR NONE

public function getId()

return $this-gt id

In the authenticate() method we use the User class to look for a row in the User tablewhose username column is the same as the given username in a case-insensitive mannerRemember that the User class was created using the yiic tool in the prior section Becausethe User class extends from CActiveRecord we can exploit the ActiveRecord feature toaccess the User table in an OOP fashion

In order to check if the user has entered a valid password we invoke the validatePassword

method of the User class We need to modify the file wwwrootblogprotectedmodels

Userphp as follows Note that instead of storing the plain password in the database westore the hash result of the password and a randomly generated salt key When validatingthe user-entered password we should compare the hash results instead

class User extends CActiveRecord

public function validatePassword($password)

return $this-gthashPassword($password$this-gtsalt)===$this-gtpassword

16 2 Initial Prototyping

public function hashPassword($password$salt)

return md5($salt$password)

In the UserIdentity class we also override the getId() method which returns the id valueof the user found in the User table The parent implementation would return the usernameinstead Both the username and id properties will be stored in the user session and maybe accessed via Yiiapp()-gtuser from anywhere in our code

Tip In the UserIdentity class we reference the class CUserIdentity without ex-plicitly including the corresponding class file This is because CUserIdentity is acore class provided by the Yii framework Yii will automatically include the classfile for any core class when it is referenced for the first time

We also do the same with the User class This is because the User class file is placedunder the directory wwwrootblogprotectedmodels which has been added tothe PHP include path according to the following lines found in the applicationconfiguration

return array(

rsquoimportrsquo=gtarray(

rsquoapplicationmodelsrsquo

rsquoapplicationcomponentsrsquo

)

)

The above configuration says that any class whose class file is located under either wwwrootblogprotectedmodels or wwwrootblogprotectedcomponents willbe automatically included when the class is referenced for the first time

The UserIdentity class is mainly used by the LoginForm class to authenticate a user basedon the username and password input collected from the login page The following codefragment shows how UserIdentity is used

$identity=new UserIdentity($username$password)

$identity-gtauthenticate()

switch($identity-gterrorCode)

24 Summary 17

case UserIdentityERROR NONE

Yiiapp()-gtuser-gtlogin($identity)

break

Info People often get confused about identity and the user application componentThe former represents a way of performing authentication while the latter is usedto represent the information related with the current user An application can onlyhave one user component but it can have one or several identity classes dependingon what kind of authentication it supports Once authenticated an identity instancemay pass its state information to the user component so that they are globallyaccessible via user

To test the modified UserIdentity class we can browse the URL httpwwwexample

comblogindexphp and try logging in with the username and password that we store inthe User table If we use the database provided by the blog demo we should be able tologin with username demo and password demo Note that this blog system does not providethe user management feature As a result a user cannot change his account or create anew one through the Web interface The user management feature may be considered asa future enhancement to the blog application

24 Summary

We have completed the milestone 1 Letrsquos summarize what we have done so far

1 We identified the requirements to be fulfilled

2 We installed the Yii framework

3 We created a skeleton application

4 We designed and created the blog database

5 We modified the application configuration by adding the database connection

6 We generated the code that implements the basic CRUD operations for both postsand comments

7 We modified the authentication method to check against the tbl user table

18 2 Initial Prototyping

For a new project most of the time will be spent in step 1 and 4 for this first milestone

Although the code generated by the yiic tool implements fully functional CRUD oper-ations for a database table it often needs to be modified in practical applications Forthis reason in the next two milestone our job is to customize the generated CRUD codeabout posts and comments so that it reaches our initial requirements

In general we first modify the model class file by adding appropriate validation rules anddeclaring relational objects We then modify the controller action and view code for eachindividual CRUD operation

Chapter 3

Post Management

31 Customizing Post Model

The Post model class generated by the yiic tool mainly needs to be modified in two places

bull the rules() method specifies the validation rules for the model attributes

bull the relations() method specifies the related objects

Info A model consists of a list of attributes each associated with a column in thecorresponding database table Attributes can be declared explicitly as class membervariables or implicitly without any declaration

311 Customizing rules() Method

We first specify the validation rules which ensure the attribute values entered by usersare correct before they are saved to the database For example the status attribute ofPost should be an integer 1 2 or 3 The yiic tool also generates validation rules for eachmodel However these rules are based on the table column information and may not beappropriate

Based on the requirement analysis we modify the rules() method as follows

public function rules()

return array(

array(rsquotitle content statusrsquo rsquorequiredrsquo)

array(rsquotitlersquo rsquolengthrsquo rsquomaxrsquo=gt128)

array(rsquostatusrsquo rsquoinrsquo rsquorangersquo=gtarray(123))

array(rsquotagsrsquo rsquomatchrsquo rsquopatternrsquo=gtrsquo^[ws]+$rsquorsquomessagersquo=gtrsquoTags can only contain word charactersrsquo)

20 3 Post Management

array(rsquotagsrsquo rsquonormalizeTagsrsquo)

)

In the above we specify that the title content and status attributes are required thelength of title should not exceed 128 the status attribute value should be 1 (draft) 2(published) or 3 (archived) and the tags attribute should only contain word charactersand commas In addition we use normalizeTags to normalize the user-entered tags so thatthe tags are unique and properly separated with commas

The validators such as required length in and match are all built-in validators providedby Yii The normalizeTags validator is a method-based validator that we need to definein the Post class For more information about how to specify validation rules please referto the Guide

public function normalizeTags($attribute$params)

$this-gttags=Tagarray2string(array unique(Tagstring2array($this-gttags)))

The rules declared in this method are executed one by one when we call the validate() orsave() method of the model instance

Note It is very important to remember that attributes appearing in rules() mustbe those to be entered by end users Other attributes such as id and create time

in the Post model which are set by our code or database should not be in rules()For more details please refer to Securing Attribute Assignments

After making these changes we can visit the post creation page again to verify that thenew validation rules are taking effect

312 Customizing relations() Method

Lastly we customize the relations() method to specify the related objects of a post Bydeclaring these related objects in relations() we can exploit the powerful RelationalActiveRecord (RAR) feature to access the related object information of a post such asits author and comments without the need to write complex SQL JOIN statements

We customize the relations() method as follows

31 Customizing Post Model 21

public function relations()

return array(

rsquoauthorrsquo =gt array(selfBELONGS TO rsquoUserrsquo rsquoauthor idrsquo)

rsquocommentsrsquo =gt array(selfHAS MANY rsquoCommentrsquo rsquopost idrsquo

rsquoconditionrsquo=gtrsquocommentsstatus=rsquoCommentSTATUS APPROVED

rsquoorderrsquo=gtrsquocommentscreate time DESCrsquo)

rsquocommentCountrsquo =gt array(selfSTAT rsquoCommentrsquo rsquopost idrsquo

rsquoconditionrsquo=gtrsquostatus=rsquoCommentSTATUS APPROVED)

)

The above relations state that

bull A post belongs to an author whose class is User and the relationship is establishedbased on the author id attribute value of the post

bull A post has many comments whose class is Comment and the relationship is establishedbased on the post id attribute value of the comments These comments should besorted according to their creation time and the comments must be approved

bull The commentCount relation is a bit special as it returns back an aggregation resultwhich is about how many comments the post has

With the above relation declaration we can easily access the author and comments of apost like the following

$author=$post-gtauthor

echo $author-gtusername

$comments=$post-gtcomments

foreach($comments as $comment)

echo $comment-gtcontent

For more details about how to declare and use relations please refer to the Guide

313 Representing Status in Text

Because the status of a post is stored as an integer in the database we need to provide atextual representation so that it is more intuitive when being displayed to end users In alarge system the similar requirement is very common

22 3 Post Management

As a generic solution we use the tbl lookup table to store the mapping between integervalues and textual representations that are needed by other data objects We modify theLookup model class as follows to more easily access the textual data in the table

class Lookup extends CActiveRecord

private static $ items=array()

public static function items($type)

if(isset(self$ items[$type]))

selfloadItems($type)

return self$ items[$type]

public static function item($type$code)

if(isset(self$ items[$type]))

selfloadItems($type)

return isset(self$ items[$type][$code]) self$ items[$type][$code] false

private static function loadItems($type)

self$ items[$type]=array()

$models=selfmodel()-gtfindAll(array(

rsquoconditionrsquo=gtrsquotype=typersquo

rsquoparamsrsquo=gtarray(rsquotypersquo=gt$type)

rsquoorderrsquo=gtrsquopositionrsquo

))

foreach($models as $model)

self$ items[$type][$model-gtcode]=$model-gtname

Our new code mainly provides two static methods Lookupitems() and Lookupitem()The former returns a list of strings belonging to the specified data type while the latterreturns a particular string for the given data type and data value

Our blog database is pre-populated with two lookup types PostStatus and CommentStatusThe former refers to the possible post statuses while the latter the comment statuses

In order to make our code easier to read we also declare a set of constants to representthe status integer values We should use these constants through our code when referringto the corresponding status values

32 Creating and Updating Posts 23

class Post extends CActiveRecord

const STATUS DRAFT=0

const STATUS PUBLISHED=1

const STATUS ARCHIVED=2

Therefore we can call Lookupitems(rsquoPostStatusrsquo) to get the list of possible post statuses(text strings indexed by the corresponding integer values) and call Lookupitem(rsquoPostStatusrsquoPostSTATUS PUBLISHED) to get the string representation of the published status

32 Creating and Updating Posts

With the Post model ready we need to fine-tune the actions and views for the controllerPostController In this section we first customize the access control of CRUD operationswe then modify the code implementing the create and update operations

321 Customizing Access Control

The first thing we want to do is to customize the access control because the code generatedby yiic does not fit our needs

We modify the accessRules() method in the file wwwrootblogprotectedcontrollers

PostControllerphp as follows

public function accessRules()

return array(

array(rsquoallowrsquo allow all users to perform rsquolistrsquo and rsquoshowrsquo actions

rsquoactionsrsquo=gtarray(rsquoindexrsquo rsquoviewrsquo)

rsquousersrsquo=gtarray(rsquorsquo)

)

array(rsquoallowrsquo allow authenticated users to perform any action

rsquousersrsquo=gtarray(rsquorsquo)

)

array(rsquodenyrsquo deny all users

rsquousersrsquo=gtarray(rsquorsquo)

)

)

The above rules state that all users can access the index and view actions and authenti-cated users can access any actions including the admin action The user should be denied

24 3 Post Management

access in any other scenario Note that these rules are evaluated in the order they arelisted here The first rule matching the current context makes the access decision Forexample if the current user is the system owner who tries to visit the post creation pagethe second rule will match and it will give the access to the user

322 Customizing create and update Operations

The create and update operations are very similar They both need to display an HTMLform to collect user inputs validate them and save them into database The main differ-ence is that the update operation will pre-populate the form with the existing post datafound in the database For this reason the yiic tool generates a partial view wwwroot

blogprotectedviewspost formphp that is embedded in both the create and update

views to render the needed HTML form

We first change the formphp file so that the HTML form only collects the inputs wewant title content tags and status We use plain text fields to collect inputs for thefirst three attributes and a dropdown list to collect input for status The dropdown listoptions are the text displays of the possible post statuses

ltphp echo CHtmlactiveDropDownList($postrsquostatusrsquoLookupitems(rsquoPostStatusrsquo)) gt

In the above we call Lookupitems(rsquoPostStatusrsquo) to bring back the list of post statuses

We then modify the Post class so that it can automatically set some attributes (eg create

time author id) before a post is saved to the database We override the beforeSave()

method as follows

protected function beforeSave()

if(parentbeforeSave())

if($this-gtisNewRecord)

$this-gtcreate time=$this-gtupdate time=time()

$this-gtauthor id=Yiiapp()-gtuser-gtid

else

$this-gtupdate time=time()

return true

else

return false

33 Displaying Posts 25

When we save a post we want to update the tbl tag table to reflect the change of tagfrequencies We can do this work in the afterSave() method which is automaticallyinvoked by Yii after a post is successfully saved into the database

protected function afterSave()

parentafterSave()

Tagmodel()-gtupdateFrequency($this-gt oldTags $this-gttags)

private $ oldTags

protected function afterFind()

parentafterFind()

$this-gt oldTags=$this-gttags

In the implementation because we want to detect if the user changes the tags in case he isupdating an existing post we need to know what the old tags are For this reason we alsowrite the afterFind() method to keep the old tags in the variable oldTags The methodafterFind() is invoked automatically by Yii when an AR record is populated with thedata from database

We are not going to give details of the TagupdateFrequency() method here Interestedreaders may refer to the file wwwrootyiidemosblogprotectedmodelsTagphp

33 Displaying Posts

In our blog application a post may be displayed among a list of posts or by itself Theformer is implemented as the index operation while the latter the view operation In thissection we customize both operations to fulfill our initial requirements

331 Customizing view Operation

The view operation is implemented by the actionView() method in PostController Itsdisplay is generated by the view view with the view file wwwrootblogprotectedviews

postviewphp

Below is the relevant code implementing the view operation in PostController

public function actionView()

26 3 Post Management

$this-gtrender(rsquoviewrsquoarray(

$post=$this-gtloadModel()

$this-gtrender(rsquoviewrsquoarray(

rsquomodelrsquo=gt$post

))

))

private $ model

public function loadModel()

if($this-gt model===null)

if(isset($ GET[rsquoidrsquo]))

if(Yiiapp()-gtuser-gtisGuest)

$condition=rsquostatus=rsquoPostSTATUS PUBLISHED

rsquo OR status=rsquoPOSTSTATUS ARCHIVED

else

$condition=rsquorsquo

$this-gt model=Postmodel()-gtfindbyPk($ GET[rsquoidrsquo] $condition)

if($this-gt model===null)

throw new CHttpException(404rsquoThe requested page does not existrsquo)

return $this-gt model

Our change mainly lies in the loadModel() method In this method we query the Post

table according to the id GET parameter If the post is not found or if it is not publishedor archived (when the user is a guest) we will throw a 404 HTTP error Otherwise thepost object is returned to actionView() which in turn passes the post object to the viewscript for further display

Tip Yii captures HTTP exceptions (instances of CHttpException) and displaysthem in either predefined templates or customized error views The skeleton appli-cation generated by yiic already contains a customized error view in wwwroot

blogprotectedviewssiteerrorphp We can modify this file if we want tofurther customize the error display

The change in the view script is mainly about ajdusting the formatting and styles of thepost display We will not go into details here Interested readers may refer to wwwroot

blogprotectedviewspostviewphp

33 Displaying Posts 27

332 Customizing index Operation

Like the view operation we customize the index operation in two places the actionIndex()

method in PostController and the view file wwwrootblogprotectedviewspostindex

php We mainly need to add the support for displaying a list of posts that are associatedwith a specified tag

Below is the modified actionIndex() method in PostController

public function actionIndex()

$criteria=new CDbCriteria(array(

rsquoconditionrsquo=gtrsquostatus=rsquoPostSTATUS PUBLISHED

rsquoorderrsquo=gtrsquoupdate time DESCrsquo

rsquowithrsquo=gtrsquocommentCountrsquo

))

if(isset($ GET[rsquotagrsquo]))

$criteria-gtaddSearchCondition(rsquotagsrsquo$ GET[rsquotagrsquo])

$dataProvider=new CActiveDataProvider(rsquoPostrsquo array(

rsquopaginationrsquo=gtarray(

rsquopageSizersquo=gtYiiapp()-gtparams[rsquopostsPerPagersquo]

)

rsquocriteriarsquo=gt$criteria

))

$this-gtrender(rsquoindexrsquoarray(

rsquodataProviderrsquo=gt$dataProvider

))

In the above we first create a query criteria for retrieving post list The criteria statesthat only published posts should be returned and they should be sorted according to theirupdate time in descending order Because when displaying a post in the list we want toshow how many comments the post has received in the criteria we also specify to bringback commentCount which if you remember is a relation declared in Postrelations()

In case when a user wants to see posts with a specific tag we would add a search conditionto the criteria to look for the specified tag

Using the query criteria we create a data provider which mainly serves for three purposesFirst it does pagination of the data when too many results may be returned Herewe customize the pagination by setting the page size to be an application parameterpostsPerPage Second it does sorting according to the user request And finally it feedsthe paginated and sorted data to widgets or view code for presentation

28 3 Post Management

After we finish with actionIndex() we modify the index view as follows Our change ismainly about adding the h1 header when the user specifies to display posts with a tag

ltphp if(empty($ GET[rsquotagrsquo])) gt

lth1gtPosts Tagged with ltigtltphp echo CHtmlencode($ GET[rsquotagrsquo]) gtltigtlth1gt

ltphp endif gt

ltphp $this-gtwidget(rsquoziiwidgetsCListViewrsquo array(

rsquodataProviderrsquo=gt$dataProvider

rsquoitemViewrsquo=gtrsquo viewrsquo

rsquotemplatersquo=gtitemsnpager)) gt

Note that in the above we use [CListView] to display the post list This widget requires apartial view to display the detail of each individual post Here we specify the partial viewto be view which means the file wwwrootblogprotectedviewspost viewphp In thisview script we can acccess the post instance being displayed via a local variable named$data

34 Managing Posts

Managing posts mainly refers to listing posts in an administrative view that allows us tosee posts with all statuses updating them and deleting them They are accomplished bythe admin operation and the delete operation respectively The code generated by yiic

does not need much modification Below we mainly explain how these two operations areimplemented

341 Listing Posts in Tabular View

The admin operation shows posts with all statuses in a tabular view The view supportssorting and pagination The following is the actionAdmin() method in PostController

public function actionAdmin()

$dataProvider=new CActiveDataProvider(rsquoPostrsquo array(

rsquosortrsquo=gtarray(

rsquodefaultOrderrsquo=gtrsquostatus update time DESCrsquo

)

))

$this-gtrender(rsquoadminrsquoarray(

rsquodataProviderrsquo=gt$dataProvider

))

34 Managing Posts 29

The above code is very similar to that in actionIndex() The main difference is in thequery criteria Here we do not specify any particular query criteria since we want to seeall posts We do customize the sort property of the data provider so that by default theposts are shown in the order of their statuses and update time

Below is the code for the admin view

lth1gtManage Postslth1gt

ltphp $this-gtwidget(rsquoziiwidgetsgridCGridViewrsquo array(

rsquodataProviderrsquo=gt$dataProvider

rsquocolumnsrsquo=gtarray(

array(

rsquonamersquo=gtrsquotitlersquo

rsquotypersquo=gtrsquorawrsquo

rsquovaluersquo=gtrsquoCHtmllink(CHtmlencode($data-gttitle) $data-gturl)rsquo

)

array(

rsquonamersquo=gtrsquostatusrsquo

rsquovaluersquo=gtrsquoLookupitem(PostStatus$data-gtstatus)rsquo

)

rsquocreate timedatetimersquo

rsquoupdate timedatetimersquo

array(

rsquoclassrsquo=gtrsquoCButtonColumnrsquo

)

)

)) gt

We use [CGridView] to display the posts It allows us to sort by a column and paginatethrough the posts if there are too many to be displayed in a single page Our change ismainly about how to display each column For example for the title column we specifythat it should be displayed as a hyperlink that points to the detailed view of the post

Tip When displaying text we call CHtmlencode() to encode HTML entities init This prevents from cross-site scripting attack

342 Deleting Posts

In the admin data grid there is a delete button in each row Clicking on the button shoulddelete the corresponding post Internally this triggers the delete action implemented asfollows

30 3 Post Management

public function actionDelete()

if(Yiiapp()-gtrequest-gtisPostRequest)

we only allow deletion via POST request

$this-gtloadModel()-gtdelete()

if(isset($ POST[rsquoajaxrsquo]))

$this-gtredirect(array(rsquoindexrsquo))

else

throw new CHttpException(400rsquoInvalid request Please do not repeat this request againrsquo)

The above code is the one generated by the yiic tool without any change We wouldlike to explain a little bit more about the checking on $ POST[rsquoajaxrsquo] The [CGridView]widget has a very nice feature that its sorting pagination and deletion operations are alldone in AJAX mode by default That means the whole page does not get reloaded if anyof the above operations is performed However it is also possible that the widget runs innon-AJAX mode (by setting its ajaxUpdate property to be false or disabling JavaScript onthe client side) It is necessary for the delete action to differentiate these two scenarios ifthe delete request is made via AJAX we should not redirect the user browser otherwisewe should

Deleting a post should also cause the deletion of all comments for that post In additionwe should also update the tbl tag table regarding the tags for the deleted post Both ofthese tasks can be achieved by writing an afterDelete method in the Post model class asfollows

protected function afterDelete()

parentafterDelete()

Commentmodel()-gtdeleteAll(rsquopost id=rsquo$this-gtid)

Tagmodel()-gtupdateFrequency($this-gttags rsquorsquo)

The above code is very straightforward it first deletes all those comments whose post id

is the same as the ID of the deleted post it then updates the tbl tag table for the tags

of the deleted post

34 Managing Posts 31

Tip We have to explicitly delete all comments for the deleted post here becauseSQLite does not really support foreign key constraints In a DBMS that supportsthis constraint (such as MySQL PostgreSQL) the foreign key constraint can be setup such that the DBMS automatically deletes the related comments if the post isdeleted In that case we no longer this explicit deletion call in our code

32 3 Post Management

Chapter 4

Comment Management

41 Customizing Comment Model

For the Comment model we mainly need to customize the rules() and attributeLabels()

methods The attributeLabels() method returns a mapping between attribute namesand attribute labels We do not need to touch relations() since the code generated bythe yiic tool is good enough

411 Customizing rules() Method

We first customize the validation rules generated by the yiic tool The following rules areused for comments

public function rules()

return array(

array(rsquocontent author emailrsquo rsquorequiredrsquo)

array(rsquoauthor email urlrsquo rsquolengthrsquo rsquomaxrsquo=gt128)

array(rsquoemailrsquorsquoemailrsquo)

array(rsquourlrsquorsquourlrsquo)

)

In the above we specify that the author email and content attributes are required thelength of author email and url cannot exceed 128 the email attribute must be a validemail address and the url attribute must be a valid URL

412 Customizing attributeLabels() Method

We then customize the attributeLabels() method to declare the label display for eachmodel attribute This method returns an array consisting of name-label pairs When wecall CHtmlactiveLabel() to display an attribute label

34 4 Comment Management

public function attributeLabels()

return array(

rsquoidrsquo =gt rsquoIdrsquo

rsquocontentrsquo =gt rsquoCommentrsquo

rsquostatusrsquo =gt rsquoStatusrsquo

rsquocreate timersquo =gt rsquoCreate Timersquo

rsquoauthorrsquo =gt rsquoNamersquo

rsquoemailrsquo =gt rsquoEmailrsquo

rsquourlrsquo =gt rsquoWebsitersquo

rsquopost idrsquo =gt rsquoPostrsquo

)

Tip If the label for an attribute is not declared in attributeLabels() an algo-rithm will be used to generate an appropriate label For example a label VerifyCode will be generated for attributes verify code or verifyCode

413 Customizing Saving Process

Because we want to record the creation time of a comment we override the beforeSave()

method of Comment like we do for the Post model

protected function beforeSave()

if(parentbeforeSave())

if($this-gtisNewRecord)

$this-gtcreate time=time()

return true

else

return false

42 Creating and Displaying Comments

In this section we implement the comment display and creation features

421 Displaying Comments

Instead of displaying and creating comments on individual pages we use the post detailpage (generated by the view action of PostController) Below the post content display

42 Creating and Displaying Comments 35

we display first a list of comments belonging to that post and then a comment creationform

In order to display comments on the post detail page we modify the view script wwwrootblogprotectedviewspostviewphp as follows

ltdiv id=commentsgt

ltphp if($model-gtcommentCountgt=1) gt

lth3gt

ltphp echo $model-gtcommentCount rsquocomment(s)rsquo gt

lth3gt

ltphp $this-gtrenderPartial(rsquo commentsrsquoarray(

rsquopostrsquo=gt$model

rsquocommentsrsquo=gt$model-gtcomments

)) gt

ltphp endif gt

ltdivgt

In the above we call renderPartial() to render a partial view named comments to displaythe list of comments belonging to the current post Note that in the view we use theexpression $model-gtcomments to retrieve the comments for the post This is valid becausewe have declared a comments relation in the Post class Evaluating this expression wouldtrigger an implicit JOIN database query to bring back the proper comments This featureis known as lazy relational query

The partial view comments is not very interesting It mainly goes through every commentand displays the detail of it Interested readers may refer to wwwrootyiidemosblog

protectedpost commentsphp

422 Creating Comments

To handle comment creation we first modify the actionView() method of PostController

as follows

public function actionView()

$post=$this-gtloadModel()

$comment=$this-gtnewComment($post)

$this-gtrender(rsquoviewrsquoarray(

rsquomodelrsquo=gt$post

rsquocommentrsquo=gt$comment

))

36 4 Comment Management

protected function newComment($post)

$comment=new Comment

if(isset($ POST[rsquoCommentrsquo]))

$comment-gtattributes=$ POST[rsquoCommentrsquo]

if($post-gtaddComment($comment))

if($comment-gtstatus==CommentSTATUS PENDING)

Yiiapp()-gtuser-gtsetFlash(rsquocommentSubmittedrsquorsquoThank yoursquo)

$this-gtrefresh()

return $comment

In the above we call the newComment() method before we render view In the newComment()

method we generate a Comment instance and check if the comment form is submitted If sowe try to add the comment for the post by calling $post-gtaddComment($comment) If it goesthrough we refresh the post detail page In case the comment needs to be approved we willshow a flash message to indicate this decision A flash message is usually a confirmationmessage displayed to end users If the user clicks on the refresh button of his browser themessage will disappear

We also need to modify wwwrootblogprotectedviewspostviewphp furthermore

ltdiv id=commentsgt

lth3gtLeave a Commentlth3gt

ltphp if(Yiiapp()-gtuser-gthasFlash(rsquocommentSubmittedrsquo)) gt

ltdiv class=successgt

ltphp echo Yiiapp()-gtuser-gtgetFlash(rsquocommentSubmittedrsquo) gt

ltdivgt

ltphp else gt

ltphp $this-gtrenderPartial(rsquocomment formrsquoarray(

rsquomodelrsquo=gt$comment

)) gt

ltphp endif gt

ltdivgtlt-- comments --gt

43 Managing Comments 37

In the above code we display the flash message if it is available If not we displaythe comment input form by rendering the partial view wwwrootblogprotectedviews

comment formphp

43 Managing Comments

Comment management includes updating deleting and approving comments These op-erations are implemented as actions in the CommentController class

431 Updating and Deleting Comments

The code generated by yiic for updating and deleting comments remains largely un-changed

432 Approving Comments

When comments are newly created they are in pending approval status and need to beapproved in order to be visible to guest users Approving a comment is mainly aboutchanging the status column of the comment

We create an actionApprove() method in CommentController as follows

public function actionApprove()

if(Yiiapp()-gtrequest-gtisPostRequest)

$comment=$this-gtloadModel()

$comment-gtapprove()

$this-gtredirect(array(rsquoindexrsquo))

else

throw new CHttpException(400rsquoInvalid requestrsquo)

In the above when the approve action is invoked via a POST request we call the approve()

method defined in the Comment model to change the status We then redirect the userbrowser to the page displaying the post that this comment belongs to

We also modify the actionIndex() method of Comment to show all comments We wouldlike to see comments pending approved to show up first

public function actionIndex()

38 4 Comment Management

$dataProvider=new CActiveDataProvider(rsquoCommentrsquo array(

rsquocriteriarsquo=gtarray(

rsquowithrsquo=gtrsquopostrsquo

rsquoorderrsquo=gtrsquotstatus tcreate time DESCrsquo

)

))

$this-gtrender(rsquoindexrsquoarray(

rsquodataProviderrsquo=gt$dataProvider

))

Notice that in the above code because both tbl post and tbl comment have columns statusand create time we need to disambiguate the corresponding column reference by prefixingthem with table alias names As described in the guide the alias for the primary table ina relational query is always t Therefore we are prefixing t to the status and create time

columns in the above code

Like the post index view the index view for CommentController uses [CListView] to displaythe comment list which in turn uses the partial view wwwrootblogprotectedviews

comment viewphp to display the detail of each individual comment We will not go intodetails here Interested readers may refer to the corresponding file in the blog demowwwrootyiidemosblogprotectedviewscomment viewphp

Chapter 5

Portlets

51 Customizing Page Layout

Features like rdquothe most recent commentsrdquo rdquotag cloudrdquo are better to be implemented inportlets A portlet is a pluggable user interface component that renders a fragment ofHTML code In this section we describe how to set up the portlet architecture for ourblog application

Before we go into details about developing portlets we first need to adjust our page layoutso that the portlets can be placed in a side bar

A page layout determines how to share common sections (eg header footer) amongdifferent pages and how to embed contents that are specific for different pages In Yiia page layout is represented as a view under the protectedviewslayouts folder Whenwe call the render() method in a controller the rendering result will be automaticallyembedded into this layout view before it is displayed to end users The default layoutview is named as main which means the wwwrootblogprotectedviewslayoutsmain

php file in our blog application

We could directly modify the main layout to add a side bar However doing so wouldmake some irrelevant pages such as about contact pages to have a side bar which is notalways desirable For this reason we need different layouts for different pages

We thus create two different layouts column1 and column2 The former displays the pagein a single column while the latter displays two columns in the page one of which is theside bar We will use column2 layout for the post and comment pages and column1 forthe rest of the pages Because both column1 and column2 still share the same header andfooter sections we let them to share the main layout

Based on the above description we create the column1 layout as follows

ltphp $this-gtbeginContent(rsquolayoutsmainrsquo) gt

ltdiv class=containergt

40 5 Portlets

ltdiv id=contentgt

ltphp echo $content gt

ltdivgtlt-- content --gt

ltdivgt

ltphp $this-gtendContent() gt

and the column2 layout as follows

ltphp $this-gtbeginContent(rsquolayoutsmainrsquo) gt

ltdiv class=containergt

ltdiv class=span-18gt

ltdiv id=contentgt

ltphp echo $content gt

ltdivgtlt-- content --gt

ltdivgt

ltdiv class=span-6 lastgt

ltdiv id=sidebargt

portlets to be placed here

ltdivgtlt-- sidebar --gt

ltdivgt

ltdivgt

ltphp $this-gtendContent() gt

As we can see both column1 and column2 are enclosed in a pair of beginContent andendContent calls They essentially use the specified view layoutsmain to decorate theenclosed content In all these layout views the variable $content refers to the dynamiccontent to be embedded into the layout

Besides customizing the layout view file we also need to adjust the CSS file wwwroot

blogcssmaincss so that the overall appearance would look like what we see in the blogdemo We will not go into details here

Info The skeleton application generated by the yiic tool uses the BluePrint CSSframework It provides a nice font reset and a robust grid system For more detailson how to use this framework please refer to its documentation

After creating the new layouts we need to set the layout property of controllers to makeuse of them So far we have three controllers SiteController PostController andCommentController We set SiteController to use column1 as the default layout whilethe rest two of the controllers use column2 for example

class PostController extends Controller

52 Creating User Menu Portlet 41

public $layout=rsquocolumn2rsquo

Tip If some view of the controller does not use the same layout as declaredby the layout property it may customize it in the view by a simple assignment$this-gtlayout=rsquonewlayoutrsquo

52 Creating User Menu Portlet

Based on the requirements analysis we need three different portlets the rdquouser menurdquoportlet the rdquotag cloudrdquo portlet and the rdquorecent commentsrdquo portlet We will develop theseportlets by extending the [CPortlet] widget provided by Yii

In this section we will develop our first concrete portlet - the user menu portlet whichdisplays a list of menu items that are only available to authenticated users The menucontains four items

bull Approve Comments a hyperlink that leads to a list of comments pending approval

bull Create New Post a hyperlink that leads to the post creation page

bull Manage Posts a hyperlink that leads to the post management page

bull Logout a link button that would log out the current user

521 Creating UserMenu Class

We create the UserMenu class to represent the logic part of the user menu portlet Theclass is saved in the file wwwrootblogprotectedcomponentsUserMenuphp which has thefollowing content

Yiiimport(rsquoziiwidgetsCPortletrsquo)

class UserMenu extends CPortlet

public function init()

$this-gttitle=CHtmlencode(Yiiapp()-gtuser-gtname)

parentinit()

42 5 Portlets

protected function renderContent()

$this-gtrender(rsquouserMenursquo)

The UserMenu class extends from the CPortlet class that we created previously It overridesboth the init() method and the renderContent() method of CPortlet The former setsthe portlet title to be the name of the current user the latter generates the portlet bodycontent by rendering a view named userMenu

Tip Notice that we have to explicitly include the CPortlet class by calling Yii

import() before we refer to it the first time This is because CPortlet is part of thezii project ndash the official extension library for Yii For performance considerationclasses in this project are not listed as core classes Therefore we have to import itbefore we use it the first time

522 Creating userMenu View

Next we create the userMenu view which is saved in the file wwwrootblogprotected

componentsviewsuserMenuphp

ltulgt

ltligtltphp echo CHtmllink(rsquoCreate New Postrsquoarray(rsquopostcreatersquo)) gtltligt

ltligtltphp echo CHtmllink(rsquoManage Postsrsquoarray(rsquopostadminrsquo)) gtltligt

ltligtltphp echo CHtmllink(rsquoApprove Commentsrsquoarray(rsquocommentindexrsquo))

rsquo (rsquo Commentmodel()-gtpendingCommentCount rsquo)rsquo gtltligt

ltligtltphp echo CHtmllink(rsquoLogoutrsquoarray(rsquositelogoutrsquo)) gtltligt

ltulgt

Info By default view files for a widget should be placed under the views sub-directory of the directory containing the widget class file The file name must bethe same as the view name

523 Using UserMenu Portlet

It is time for us to make use of our newly completed UserMenu portlet We modify thelayout view file wwwrootblogprotectedviewslayoutscolumn2php as follows

53 Creating Tag Cloud Portlet 43

ltdiv id=sidebargt

ltphp if(Yiiapp()-gtuser-gtisGuest) $this-gtwidget(rsquoUserMenursquo) gt

ltdivgt

In the above we call the widget() method to generate and execute an instance of theUserMenu class Because the portlet should only be displayed to authenticated users weonly call widget() when the isGuest property of the current user is false (meaning theuser is authenticated)

524 Testing UserMenu Portlet

Letrsquos test what we have so far

1 Open a browser window and enter the URL httpwwwexamplecomblogindex

php Verify that there is nothing displayed in the side bar section of the page

2 Click on the Login hyperlink and fill out the login form to login If successful verifythat the UserMenu portlet appears in the side bar and the portlet has the usernameas its title

3 Click on the rsquoLogoutrsquo hyperlink in the UserMenu portlet Verify that the logout actionis successful and the UserMenu portlet disappears

525 Summary

What we have created is a portlet that is highly reusable We can easily reuse it in adifferent project with little or no modification Moreover the design of this portlet followsclosely the philosophy that logic and presentation should be separated While we did notpoint this out in the previous sections such practice is used nearly everywhere in a typicalYii application

53 Creating Tag Cloud Portlet

Tag cloud displays a list of post tags with visual decorations hinting the popularity of eachindividual tag

531 Creating TagCloud Class

We create the TagCloud class in the file wwwrootblogprotectedcomponentsTagCloud

php The file has the following content

44 5 Portlets

Yiiimport(rsquoziiwidgetsCPortletrsquo)

class TagCloud extends CPortlet

public $title=rsquoTagsrsquo

public $maxTags=20

protected function renderContent()

$tags=Tagmodel()-gtfindTagWeights($this-gtmaxTags)

foreach($tags as $tag=gt$weight)

$link=CHtmllink(CHtmlencode($tag) array(rsquopostindexrsquorsquotagrsquo=gt$tag))

echo CHtmltag(rsquospanrsquo array(

rsquoclassrsquo=gtrsquotagrsquo

rsquostylersquo=gtfont-size$weightpt) $link)n

Unlike the UserMenu portlet the TagCloud portlet does not use a view Instead its presen-tation is done in the renderContent() method This is because the presentation does notcontain much HTML tags

We display each tag as a hyperlink to the post index page with the corresponding tagparameter The font size of each tag link is adjusted according to their relative weightamong other tags If a tag has higher frequency value than the other it will have a biggerfont size

532 Using TagCloud Portlet

Usage of the TagCloud portlet is very simple We modify the layout file wwwrootblog

protectedviewslayoutsmainphp as follows

ltdiv id=sidebargt

ltphp if(Yiiapp()-gtuser-gtisGuest) $this-gtwidget(rsquoUserMenursquo) gt

ltphp $this-gtwidget(rsquoTagCloudrsquo array(

rsquomaxTagsrsquo=gtYiiapp()-gtparams[rsquotagCloudCountrsquo]

)) gt

ltdivgt

54 Creating Recent Comments Portlet 45

54 Creating Recent Comments Portlet

In this section we create the last portlet that displays a list of comments recently pub-lished

541 Creating RecentComments Class

We create the RecentComments class in the file wwwrootblogprotectedcomponentsRecentCommentsphp The file has the following content

Yiiimport(rsquoziiwidgetsCPortletrsquo)

class RecentComments extends CPortlet

public $title=rsquoRecent Commentsrsquo

public $maxComments=10

public function getRecentComments()

return Commentmodel()-gtfindRecentComments($this-gtmaxComments)

protected function renderContent()

$this-gtrender(rsquorecentCommentsrsquo)

In the above we invoke the findRecentComments method which is defined in the Comment

class as follows

class Comment extends CActiveRecord

public function findRecentComments($limit=10)

return $this-gtwith(rsquopostrsquo)-gtfindAll(array(

rsquoconditionrsquo=gtrsquotstatus=rsquoselfSTATUS APPROVED

rsquoorderrsquo=gtrsquotcreate time DESCrsquo

rsquolimitrsquo=gt$limit

))

46 5 Portlets

542 Creating recentComments View

The recentComments view is saved in the file wwwrootblogprotectedcomponentsviews

recentCommentsphp It simply displays every comment returned by the RecentComments

getRecentComments() method

543 Using RecentComments Portlet

We modify the layout file wwwrootblogprotectedviewslayoutsmainphp to embedthis last portlet

ltdiv id=sidebargt

ltphp if(Yiiapp()-gtuser-gtisGuest) $this-gtwidget(rsquoUserMenursquo) gt

ltphp $this-gtwidget(rsquoTagCloudrsquo array(

rsquomaxTagsrsquo=gtYiiapp()-gtparams[rsquotagCloudCountrsquo]

)) gt

ltphp $this-gtwidget(rsquoRecentCommentsrsquo array(

rsquomaxCommentsrsquo=gtYiiapp()-gtparams[rsquorecentCommentCountrsquo]

)) gt

ltdivgt

Chapter 6

Final Work

61 Beautifying URLs

The URLs linking various pages of our blog application currently look ugly For examplethe URL for the page showing a post looks like the following

indexphpr=postshowampid=1

In this section we describe how to beautify these URLs and make them SEO-friendlyOur goal is to be able to use the following URLs in the application

1 indexphppostsyii leads to the page showing a list of posts with tag yii

2 indexphppost2A+Test+Post leads to the page showing the detail of the postwith ID 2 whose title is A Test Post

3 indexphppostupdate1 leads to the page that allows updating the post with ID1

Note that in the second URL format we include the post title in the URL This is mainlyto make the URL SEO friendly It is said that search engines may also respect the wordsfound in a URL when it is being indexed

To achieve our goal we modify the application configuration as follows

return array(

rsquocomponentsrsquo=gtarray(

rsquourlManagerrsquo=gtarray(

rsquourlFormatrsquo=gtrsquopathrsquo

rsquorulesrsquo=gtarray(

48 6 Final Work

rsquopostltidd+gtlttitlegtrsquo=gtrsquopostviewrsquorsquopostslttaggtrsquo=gtrsquopostindexrsquo

)

)

)

)

In the above we configure the urlManager component by setting its urlFormat propertyto be path and adding a set of rules

The rules are used by urlManager to parse and create the URLs in the desired formatFor example the first rule says that if a URL indexphppostsyii is requested theurlManager component should be responsible to dispatch the request to the route post

index and generate a tag GET parameter with the value yii On the other hand whencreating a URL with the route postindex and parameter tag the urlManager componentwill also use this rule to generate the desired URL indexphppostsyii For this reasonwe say that urlManager is a two-way URL manager

The urlManager component can further beautify our URLs such as hiding indexphp inthe URLs appending suffix like html to the URLs We can obtain these features easilyby configuring various properties of urlManager in the application configuration For moredetails please refer to the Guide

62 Logging Errors

A production Web application often needs sophisticated logging for various events In ourblog application we would like to log the errors occurring when it is being used Sucherrors could be programming mistakes or usersrsquo misuse of the system Logging these errorswill help us to improve the blog application

We enable the error logging by modifying the application configuration as follows

return array(

rsquopreloadrsquo=gtarray(rsquologrsquo)

rsquocomponentsrsquo=gtarray(

rsquologrsquo=gtarray(

rsquoclassrsquo=gtrsquoCLogRouterrsquo

rsquoroutesrsquo=gtarray(

array(

rsquoclassrsquo=gtrsquoCFileLogRoutersquo

rsquolevelsrsquo=gtrsquoerror warningrsquo

63 Final Tune-up and Deployment 49

)

)

)

)

)

With the above configuration if an error or warning occurs detailed information will belogged and saved in a file located under the directory wwwrootblogprotectedruntime

The log component offers more advanced features such as sending log messages to a listof email addresses displaying log messages in JavaScript console window etc For moredetails please refer to the Guide

63 Final Tune-up and Deployment

We are close to finish our blog application Before deployment we would like to do sometune-ups

631 Changing Home Page

We change to use the post list page as the home page We modify the application config-uration as follows

return array(

rsquodefaultControllerrsquo=gtrsquopostrsquo

)

Tip Because PostController already declares list to be its default action whenwe access the home page of the application we will see the result generated by thelist action of the post controller

632 Enabling Schema Caching

Because ActiveRecord relies on the metadata about tables to determine the column infor-mation it takes time to read the metadata and analyze it This may not be a problemduring development stage but for an application running in production mode it is a totalwaste of time if the database schema does not change Therefore we should enable theschema caching by modifying the application configuration as follows

50 6 Final Work

return array(

rsquocomponentsrsquo=gtarray(

rsquocachersquo=gtarray(

rsquoclassrsquo=gtrsquoCDbCachersquo

)

rsquodbrsquo=gtarray(

rsquoclassrsquo=gtrsquosystemdbCDbConnectionrsquo

rsquoconnectionStringrsquo=gtrsquosqlitewwwrootblogprotecteddatablogdbrsquo

rsquoschemaCachingDurationrsquo=gt3600

)

)

)

In the above we first add a cache component which uses a default SQLite database as thecaching storage If our server is equipped with other caching extensions such as APCwe could change to use them as well We also modify the db component by setting itsschemaCachingDuration property to be 3600 which means the parsed database schemadata can remain valid in cache for 3600 seconds

633 Disabling Debugging Mode

We modify the entry script file wwwrootblogindexphp by removing the line definingthe constant YII DEBUG This constant is useful during development stage because it allowsYii to display more debugging information when an error occurs However when theapplication is running in production mode displaying debugging information is not agood idea because it may contain sensitive information such as where the script file islocated and the content in the file etc

634 Deploying the Application

The final deployment process manly involves copying the directory wwwrootblog to thetarget directory The following checklist shows every needed step

1 Install Yii in the target place if it is not available

2 Copy the entire directory wwwrootblog to the target place

3 Edit the entry script file indexphp by pointing the $yii variable to the new Yiibootstrap file

4 Edit the file protectedyiicphp by setting the $yiic variable to be the new Yiiyiicphp file

64 Future Enhancements 51

5 Change the permission of the directories assets and protectedruntime so that theyare writable by the Web server process

64 Future Enhancements

641 Using a Theme

Without writing any code our blog application is already themeable To use a themewe mainly need to develop the theme by writing customized view files in the theme Forexample to use a theme named classic that uses a different page layout we would createa layout view file wwwrootblogthemesclassicviewslayoutsmainphp We also needto change the application configuration to indicate our choice of the classic theme

return array(

rsquothemersquo=gtrsquoclassicrsquo

)

642 Internationalization

We may also internationalize our blog application so that its pages can be displayed indifferent languages This mainly involves efforts in two aspects

First we may create view files in different languages For example for the list pageof PostController we can create a view file wwwrootblogprotectedviewspostzh cn

listphp When the application is configured to use simplified Chinese (the language codeis zh cn) Yii will automatically use this new view file instead of the original one

Second we may create message translations for those messages generated by code Themessage translations should be saved as files under the directory wwwrootblogprotected

messages We also need to modify the code where we use text strings by enclosing themin the method call Yiit()

For more details about internationalization please refer to the Guide

643 Improving Performance with Cache

While the Yii framework itself is very efficient it is not necessarily true that an applicationwritten in Yii efficient There are several places in our blog application that we can improvethe performance For example the tag clould portlet could be one of the performancebottlenecks because it involves complex database query and PHP logic

52 6 Final Work

We can make use of the sophisticated caching feature provided by Yii to improve theperformance One of the most useful components in Yii is COutputCache which caches afragment of page display so that the underlying code generating the fragment does not needto be executed for every request For example in the layout file wwwrootblogprotected

viewslayoutsmainphp we can enclose the tag cloud portlet with COutputCache

ltphp if($this-gtbeginCache(rsquotagCloudrsquo array(rsquodurationrsquo=gt3600))) gt

ltphp $this-gtwidget(rsquoTagCloudrsquo array(

rsquomaxTagsrsquo=gtYiiapp()-gtparams[rsquotagCloudCountrsquo]

)) gt

ltphp $this-gtendCache() gt

With the above code the tag cloud display will be served from cache instead of beinggenerated on-the-fly for every request The cached content will remain valid in cache for3600 seconds

644 Adding New Features

Our blog application only has very basic functionalities To become a complete blogsystem more features are needed for example calendar portlet email notifications postcategorization archived post portlet and so on We will leave the implementation of thesefeatures to interested readers

  • Contents
  • License
  • Getting Started
    • Building a Blog System using Yii
    • Testdriving with Yii
      • Installing Yii
      • Creating Skeleton Application
      • Application Workflow
        • Requirements Analysis
        • Overall Design
          • Initial Prototyping
            • Setting Up Database
              • Creating Database
              • Establishing Database Connection
                • Scaffolding
                • Authenticating User
                • Summary
                  • Post Management
                    • Customizing Post Model
                      • Customizing rules() Method
                      • Customizing relations() Method
                      • Representing Status in Text
                        • Creating and Updating Posts
                          • Customizing Access Control
                          • Customizing create and update Operations
                            • Displaying Posts
                              • Customizing view Operation
                              • Customizing index Operation
                                • Managing Posts
                                  • Listing Posts in Tabular View
                                  • Deleting Posts
                                      • Comment Management
                                        • Customizing Comment Model
                                          • Customizing rules() Method
                                          • Customizing attributeLabels() Method
                                          • Customizing Saving Process
                                            • Creating and Displaying Comments
                                              • Displaying Comments
                                              • Creating Comments
                                                • Managing Comments
                                                  • Updating and Deleting Comments
                                                  • Approving Comments
                                                      • Portlets
                                                        • Customizing Page Layout
                                                        • Creating User Menu Portlet
                                                          • Creating UserMenu Class
                                                          • Creating userMenu View
                                                          • Using UserMenu Portlet
                                                          • Testing UserMenu Portlet
                                                          • Summary
                                                            • Creating Tag Cloud Portlet
                                                              • Creating TagCloud Class
                                                              • Using TagCloud Portlet
                                                                • Creating Recent Comments Portlet
                                                                  • Creating RecentComments Class
                                                                  • Creating recentComments View
                                                                  • Using RecentComments Portlet
                                                                      • Final Work
                                                                        • Beautifying URLs
                                                                        • Logging Errors
                                                                        • Final Tune-up and Deployment
                                                                          • Changing Home Page
                                                                          • Enabling Schema Caching
                                                                          • Disabling Debugging Mode
                                                                          • Deploying the Application
                                                                            • Future Enhancements
                                                                              • Using a Theme
                                                                              • Internationalization
                                                                              • Improving Performance with Cache
                                                                              • Adding New Features
Page 8: Yii Blog 1.1.0 Demo Tutor

Chapter 1

Getting Started

11 Building a Blog System using Yii

This tutorial describes how to use Yii to develop a blog application shown as the blog demowhich can be found in the Yii release files It explains in detail every step to be takenduring the development which may also be applied in developing other Web applicationsAs a complement to the Guide and the Class Reference of Yii this tutorial aims to showpractical usage of Yii instead of thorough and definitive description

Readers of this tutorial are not required to have prior knowledge about Yii However basicknowledge of object-oriented programming (OOP) and database programming would helpreaders to understand the tutorial more easily

This tutorial is released under the Terms of Yii Documentation

12 Testdriving with Yii

In this section we describe how to create a skeleton application that will serve as ourstarting point For simplicity we assume that the document root of our Web server iswwwroot and the corresponding URL is httpwwwexamplecom

121 Installing Yii

We first install the Yii framework Grab a copy of the Yii release file (version 110 orabove) from wwwyiiframeworkcom and unpack it to the directory wwwrootyii Doublecheck to make sure that there is a directory wwwrootyiiframework

Tip The Yii framework can be installed anywhere in the file system not necessarilyunder a Web folder Its framework directory contains all framework code and isthe only framework directory needed when deploying an Yii application A singleinstallation of Yii can be used by multiple Yii applications

2 1 Getting Started

After installing Yii open a browser window and access the URL httpwwwexample

comyiirequirementsindexphp It shows the requirement checker provided in the Yiirelease For our blog application besides the minimal requirements needed by Yii we alsoneed to enable both the pdo and pdo sqlite PHP extensions so that we can access SQLitedatabases

122 Creating Skeleton Application

We then use the yiic tool to create a skeleton application under the directory wwwroot

blog The yiic tool is a command line tool provided in the Yii release It can be used togenerate code to reduce certain repetitive coding tasks

Open a command window and execute the following command

wwwrootyiiframeworkyiic webapp wwwrootblog

Create a Web application under rsquowwwrootblogrsquo [Yes|No]y

Tip In order to use the yiic tool as shown above the CLI PHP program must beon the command search path If not the following command may be used instead

pathtophp wwwrootyiiframeworkyiicphp webapp wwwrootblog

To try out the application we just created open a Web browser and navigate to the URLhttpwwwexamplecomblogindexphp We should see that our skeleton applicationalready has four fully functional pages the homepage the about page the contact pageand the login page

In the following we briefly describe what we have in this skeleton application

Entry Script

We have an entry script file wwwrootblogindexphp which has the following content

ltphp

$yii=rsquowwwrootframeworkyiiphprsquo

$config=dirname( FILE )rsquoprotectedconfigmainphprsquo

remove the following line when in production mode

defined(rsquoYII DEBUGrsquo) or define(rsquoYII DEBUGrsquotrue)

12 Testdriving with Yii 3

require once($yii)

YiicreateWebApplication($config)-gtrun()

This is the only script that Web users can directly access The script first includes theYii bootstrap file yiiphp It then creates an application instance with the specifiedconfiguration and executes the application

Base Application Directory

We also have an application base directory wwwrootblogprotected The majority of ourcode and data will be placed under this directory and it should be protected from beingaccessed by Web users For Apache httpd Web server we place under this directory ahtaccess file with the following content

deny from all

For other Web servers please refer to the corresponding manual on how to protect adirectory from being accessed by Web users

123 Application Workflow

To help understand how Yii works we describe the main workflow in our skeleton appli-cation when a user is accessing its contact page

1 The user requests the URL httpwwwexamplecomblogindexphpr=sitecontact

2 The entry script is executed by the Web server to process the request

3 An application instance is created and configured with initial property values speci-fied in the application configuration file wwwrootblogprotectedconfigmainphp

4 The application resolves the request into a controller and a controller action For thecontact page request it is resolved as the site controller and the contact action (theactionContact method in wwwrootblogprotectedcontrollersSiteControllerphp)

5 The application creates the site controller in terms of a SiteController instanceand then executes it

6 The SiteController instance executes the contact action by calling its actionContact()method

4 1 Getting Started

7 The actionContact method renders a view named contact to the Web user Inter-nally this is achieved by including the view file wwwrootblogprotectedviews

sitecontactphp and embedding the result into the layout file wwwrootblog

protectedviewslayoutsmainphp

13 Requirements Analysis

The blog system that we are going to develop is a single user system The owner of thesystem will be able to perform the following actions

bull Login and logout

bull Create update and delete posts

bull Publish unpublish and archive posts

bull Approve and delete comments

All other users are guest users who can perform the following actions

bull Read posts

bull Create comments

Additional Requirements for this system include

bull The homepage of the system should display a list of the most recent posts

bull If a page contains more than 10 posts they should be displayed in pages

bull The system should display a post together with its comments

bull The system should be able to list posts with a specified tag

bull The system should show a cloud of tags indicating their use frequencies

bull The system should show a list of most recent comments

bull The system should be themeable

bull The system should use SEO-friendly URLs

14 Overall Design 5

14 Overall Design

Based on the analysis of the requirements we decide to use the following database tablesto store the persistent data for our blog application

bull tbl user stores the user information including username and password

bull tbl post stores the blog post information It mainly consists of the following columns

ndash title required title of the post

ndash content required body content of the post which uses the Markdown format

ndash status required status of the post which can be one of following values

lowast 1 meaning the post is in draft and is not visible to public

lowast 2 meaning the post is published to public

lowast 3 meaning the post is outdated and is not visible in the post list (stillaccessible individually though)

ndash tags optional a list of comma-separated words categorizing the post

bull tbl comment stores the post comment information Each comment is associated witha post and mainly consists of the following columns

ndash name required the author name

ndash email required the author email

ndash website optional the author website URL

ndash content required the comment content in plain text format

ndash status required status of the comment which indicates whether the commentis approved (value 2) or not (value 1)

bull tbl tag stores post tag frequency information that is needed to implement the tagcloud feature The table mainly contains the following columns

ndash name required the unique tag name

ndash frequency required the number of times that the tag appears in posts

bull tbl lookup stores generic lookup information It is essentially a map between integervalues and text strings The former is the data representation in our code while thelatter is the corresponding presentation to end users For example we use integer1 to represent the draft post status and string Draft to display this status to endusers This table mainly contains the following columns

6 1 Getting Started

ndash name the textual representation of the data item that is to be displayed to endusers

ndash code the integer representation of the data item

ndash type the type of the data item

ndash position the relative display order of the data item among other items of thesame type

The following entity-relation (ER) diagram shows the table structure and relationshipsabout the above tables

Figure 11 Entity-Relation Diagram of the Blog Database

Complete SQL statements corresponding to the above ER diagram may be found inthe blog demo In our Yii installation they are in the file wwwrootyiidemosblog

protecteddataschemasqlitesql

Info We name all our table names and column names in lower case This is becausedifferent DBMS often have different case-sensitivity treatment and we want to avoidtroubles like this

We also prefix all our tables with tbl This serves for two purposes First the prefixintroduces a namespace to these tables in case when they need to coexist with othertables in the same database which often happens in a shared hosting environmentwhere a single database is being used by multiple applications Second using tableprefix reduces the possibility of having some table names that are reserved keywordsin DBMS

14 Overall Design 7

We divide the development of our blog application into the following milestones

bull Milestone 1 creating a prototype of the blog system It should consist of most ofthe required functionalities

bull Milestone 2 completing post management It includes creating listing showingupdating and deleting posts

bull Milestone 3 completing comment management It includes creating listing ap-proving updating and deleting post comments

bull Milestone 4 implementing portlets It includes user menu login tag cloud andrecent comments portlets

bull Milestone 5 final tune-up and deployment

8 1 Getting Started

Chapter 2

Initial Prototyping

21 Setting Up Database

Having created a skeleton application and finished the database design in this section wewill create the blog database and establish the connection to it in the skeleton application

211 Creating Database

We choose to create a SQLite database Because the database support in Yii is built on topof PDO we can easily switch to use a different type of DBMS (eg MySQL PostgreSQL)without the need to change our application code

We create the database file blogdb under the directory wwwrootblogprotecteddataNote that both the directory and the database file have to be writable by the Web serverprocess as required by SQLite We may simply copy the database file from the blogdemo in our Yii installation which is located at wwwrootyiidemosblogprotecteddatablogdb We may also generate the database by executing the SQL statements in the filewwwrootyiidemosblogprotecteddataschemasqlitesql

Tip To execute SQL statements we may use the sqlite3 command line tool thatcan be found in the SQLite official website

212 Establishing Database Connection

To use the blog database in the skeleton application we created we need to modify itsapplication configuration which is stored in the PHP script wwwrootblogprotected

configmainphp The script returns an associative array consisting of name-value pairseach of which is used to initialize a writable property of the application instance

We configure the db component as follows

10 2 Initial Prototyping

return array(

rsquocomponentsrsquo=gtarray(

rsquodbrsquo=gtarray(

rsquoconnectionStringrsquo=gtrsquosqlitewwwrootblogprotecteddatablogdbrsquo

rsquotablePrefixrsquo=gtrsquotbl rsquo

)

)

)

The above configuration says that we have a db application component whose connectionStringproperty should be initialized as sqlitewwwrootblogprotecteddatablogdb and whosetablePrefix property should be tbl

With this configuration we can access the DB connection object using Yiiapp()-gtdb

at any place in our code Note that Yiiapp() returns the application instance that wecreate in the entry script If you are interested in possible methods and properties that theDB connection has you may refer to its class reference However in most cases we are notgoing to use this DB connection directly Instead we will use the so-called ActiveRecordto access the database

We would like to explain a bit more about the tablePrefix property that we set in theconfiguration This tells the db connection that it should respect the fact we are usingtbl as the prefix to our database table names In particular if in a SQL statement thereis a token enclosed within double curly brackets (eg post then the db connectionshould translate it into a name with the table prefix (eg tbl post) before sending it toDBMS for execution This feature is especially useful if in future we need to modify thetable name prefix without touching our source code For example if we are developing ageneric content management system (CMS) we may exploit this feature so that when itis being installed in a new environment we can allow users to choose a table prefix theylike

22 Scaffolding 11

Tip If you want to use MySQL instead of SQLite to store data you may cre-ate a MySQL database named blog using the SQL statements in wwwrootyii

demosblogprotecteddataschemamysqlsql Then modify the applicationconfiguration as follows

return array(

rsquocomponentsrsquo=gtarray(

rsquodbrsquo=gtarray(

rsquoconnectionStringrsquo =gt rsquomysqlhost=localhostdbname=blogrsquo

rsquoemulatePreparersquo =gt true

rsquousernamersquo =gt rsquorootrsquo

rsquopasswordrsquo =gt rsquorsquo

rsquocharsetrsquo =gt rsquoutf8rsquo

rsquotablePrefixrsquo =gt rsquotbl rsquo

)

)

)

22 Scaffolding

Create read update and delete (CRUD) are the four basic operations of data objects in anapplication Because the task of implementing the CRUD operations is so common whendeveloping Web applications Yii provides a tool to automate this process (also known asscaffolding) In this section we will describe how to use the tool to implement CRUD forposts and comments

Open a command window and run the following commands

wwwrootyiiframeworkyiic shell wwwrootblogindexphp

Yii Interactive Tool v11

Please type rsquohelprsquo for help Type rsquoexitrsquo to quit

gtgt model

gtgt crud Post

gtgt crud Comment

gtgt exit

12 2 Initial Prototyping

Info Some PHP installations may use a different phpini file for command line(CLI) PHP parser As a result when running the above yiic commands youmay encounter errors like rdquoYiiBaseinclude(PDOphp) failed to open streamrdquoor rdquocould not find driverrdquo Please double check your CLI PHP configuration byexecuting the following command

php -r phpinfo()

The result of the above command will show which phpini file is being used andwhich extensions are loaded If a wrong phpini file is used you may use thefollowing command to explicitly specify the correct phpini to use

php -c phpini wwwrootyiiframeworkyiicphp shell wwwrootblogindexphp

The commands above accomplish two tasks First the model command generates a modelclass file for each table in the blog database Second the crud commands generate thecode needed by the CRUD operations for the Post and Comment models

Tip The command model generates a model class for every table in the databaseSometimes we probably donrsquot want to do so (eg the database contains some irrel-evant tables) In this case we can create model classes one by one For exampleto create the User model we can use the command model User The model com-mand also has some more advanced usages For more details execute help model

command

We can test the generated code by accessing the following URLs

httpwwwexamplecomblogindexphpr=post

httpwwwexamplecomblogindexphpr=comment

Notice that the post and comment features implemented by the generated code are com-pletely independent of each other Also when creating a new post or comment we arerequired to enter information such as author id and create time which in real applica-tion should be set by the program Donrsquot worry We will fix these problems in the nextmilestones For now we should be fairly satisfied as this prototype already contains mostfeatures that we need to implement for the blog application

To prepare for the next milestones letrsquos take a closer look at the files generated by theabove commands All the files are generated under wwwrootblogprotected For conve-nience we group them into model files controller files and view files

22 Scaffolding 13

bull model files

ndash modelsUserphp contains the User class that extends from CActiveRecord andcan be used to access the tbl user database table

ndash modelsPostphp contains the Post class that extends from CActiveRecord andcan be used to access the tbl post database table

ndash modelsTagphp contains the Tag class that extends from CActiveRecord andcan be used to access the tbl tag database table

ndash modelsCommentphp contains the Comment class that extends from CActiveRe-cord and can be used to access the tbl comment database table

ndash modelsLookupphp contains the Lookup class that extends from CActiveRecordand can be used to access the tbl lookup database table

bull controller file

ndash controllersPostControllerphp contains the PostController class which is thecontroller in charge of all CRUD operations about posts

ndash controllersCommentControllerphp contains the CommentController class whichis the controller in charge of all CRUD operations about comments

bull view files

ndash viewspostcreatephp is the view file that shows an HTML form to create anew post

ndash viewspostupdatephp is the view file that shows an HTML form to update anexisting post

ndash viewspostviewphp is the view file that displays the detailed information of apost

ndash viewspostindexphp is the view file that displays a list of posts

ndash viewspostadminphp is the view file that displays posts in a table with ad-ministrative commands

ndash viewspost formphp is the partial view file embedded in viewspostcreate

php and viewspostupdatephp It displays the HTML form for collecting postinformation

ndash viewspost viewphp is the partial view file used by viewspostindexphp Itdisplays the brief view of a single post

ndash a similar set of view files are also generated for comment

14 2 Initial Prototyping

In order to understand better how the above files are used we show in the following theworkflow that occurs in the blog application when displaying a list of posts

1 The user requests the URL httpwwwexamplecomblogindexphpr=posts

2 The entry script is executed by the Web server which creates and initializes anapplication instance to handle the request

3 The application creates an instance of PostController and executes it

4 The PostController instance executes the index action by calling its actionIndex()

method Note that index is the default action if the user does not specify an actionto execute in the URL

5 The actionIndex() method queries database to bring back the list of recent posts

6 The actionIndex() method renders the index view with the post data

23 Authenticating User

Our blog application needs to differentiate between the system owner and guest usersTherefore we need to implement the user authentication feature

As you may have found that the skeleton application already provides user authenticationby checking if the username and password are both demo or admin In this section wewill modify the corresponding code so that the authentication is done against the User

database table

User authentication is performed in a class implementing the IUserIdentity interface Theskeleton application uses the UserIdentity class for this purpose The class is stored inthe file wwwrootblogprotectedcomponentsUserIdentityphp

Tip By convention the name of a class file must be the same as the correspondingclass name suffixed with the extension php Following this convention one canrefer to a class using a path alias For example we can refer to the UserIdentity

class with the alias applicationcomponentsUserIdentity Many APIs in Yiican recognize path aliases (eg YiicreateComponent()) and using path aliasesavoids the necessity of embedding absolute file paths in the code The existence ofthe latter often causes trouble when we deploy an application

We modify the UserIdentity class as follows

23 Authenticating User 15

ltphp

class UserIdentity extends CUserIdentity

private $ id

public function authenticate()

$username=strtolower($this-gtusername)

$user=Usermodel()-gtfind(rsquoLOWER(username)=rsquoarray($username))

if($user===null)

$this-gterrorCode=selfERROR USERNAME INVALID

else if($user-gtvalidatePassword($this-gtpassword))

$this-gterrorCode=selfERROR PASSWORD INVALID

else

$this-gt id=$user-gtid

$this-gtusername=$user-gtusername

$this-gterrorCode=selfERROR NONE

return $this-gterrorCode==selfERROR NONE

public function getId()

return $this-gt id

In the authenticate() method we use the User class to look for a row in the User tablewhose username column is the same as the given username in a case-insensitive mannerRemember that the User class was created using the yiic tool in the prior section Becausethe User class extends from CActiveRecord we can exploit the ActiveRecord feature toaccess the User table in an OOP fashion

In order to check if the user has entered a valid password we invoke the validatePassword

method of the User class We need to modify the file wwwrootblogprotectedmodels

Userphp as follows Note that instead of storing the plain password in the database westore the hash result of the password and a randomly generated salt key When validatingthe user-entered password we should compare the hash results instead

class User extends CActiveRecord

public function validatePassword($password)

return $this-gthashPassword($password$this-gtsalt)===$this-gtpassword

16 2 Initial Prototyping

public function hashPassword($password$salt)

return md5($salt$password)

In the UserIdentity class we also override the getId() method which returns the id valueof the user found in the User table The parent implementation would return the usernameinstead Both the username and id properties will be stored in the user session and maybe accessed via Yiiapp()-gtuser from anywhere in our code

Tip In the UserIdentity class we reference the class CUserIdentity without ex-plicitly including the corresponding class file This is because CUserIdentity is acore class provided by the Yii framework Yii will automatically include the classfile for any core class when it is referenced for the first time

We also do the same with the User class This is because the User class file is placedunder the directory wwwrootblogprotectedmodels which has been added tothe PHP include path according to the following lines found in the applicationconfiguration

return array(

rsquoimportrsquo=gtarray(

rsquoapplicationmodelsrsquo

rsquoapplicationcomponentsrsquo

)

)

The above configuration says that any class whose class file is located under either wwwrootblogprotectedmodels or wwwrootblogprotectedcomponents willbe automatically included when the class is referenced for the first time

The UserIdentity class is mainly used by the LoginForm class to authenticate a user basedon the username and password input collected from the login page The following codefragment shows how UserIdentity is used

$identity=new UserIdentity($username$password)

$identity-gtauthenticate()

switch($identity-gterrorCode)

24 Summary 17

case UserIdentityERROR NONE

Yiiapp()-gtuser-gtlogin($identity)

break

Info People often get confused about identity and the user application componentThe former represents a way of performing authentication while the latter is usedto represent the information related with the current user An application can onlyhave one user component but it can have one or several identity classes dependingon what kind of authentication it supports Once authenticated an identity instancemay pass its state information to the user component so that they are globallyaccessible via user

To test the modified UserIdentity class we can browse the URL httpwwwexample

comblogindexphp and try logging in with the username and password that we store inthe User table If we use the database provided by the blog demo we should be able tologin with username demo and password demo Note that this blog system does not providethe user management feature As a result a user cannot change his account or create anew one through the Web interface The user management feature may be considered asa future enhancement to the blog application

24 Summary

We have completed the milestone 1 Letrsquos summarize what we have done so far

1 We identified the requirements to be fulfilled

2 We installed the Yii framework

3 We created a skeleton application

4 We designed and created the blog database

5 We modified the application configuration by adding the database connection

6 We generated the code that implements the basic CRUD operations for both postsand comments

7 We modified the authentication method to check against the tbl user table

18 2 Initial Prototyping

For a new project most of the time will be spent in step 1 and 4 for this first milestone

Although the code generated by the yiic tool implements fully functional CRUD oper-ations for a database table it often needs to be modified in practical applications Forthis reason in the next two milestone our job is to customize the generated CRUD codeabout posts and comments so that it reaches our initial requirements

In general we first modify the model class file by adding appropriate validation rules anddeclaring relational objects We then modify the controller action and view code for eachindividual CRUD operation

Chapter 3

Post Management

31 Customizing Post Model

The Post model class generated by the yiic tool mainly needs to be modified in two places

bull the rules() method specifies the validation rules for the model attributes

bull the relations() method specifies the related objects

Info A model consists of a list of attributes each associated with a column in thecorresponding database table Attributes can be declared explicitly as class membervariables or implicitly without any declaration

311 Customizing rules() Method

We first specify the validation rules which ensure the attribute values entered by usersare correct before they are saved to the database For example the status attribute ofPost should be an integer 1 2 or 3 The yiic tool also generates validation rules for eachmodel However these rules are based on the table column information and may not beappropriate

Based on the requirement analysis we modify the rules() method as follows

public function rules()

return array(

array(rsquotitle content statusrsquo rsquorequiredrsquo)

array(rsquotitlersquo rsquolengthrsquo rsquomaxrsquo=gt128)

array(rsquostatusrsquo rsquoinrsquo rsquorangersquo=gtarray(123))

array(rsquotagsrsquo rsquomatchrsquo rsquopatternrsquo=gtrsquo^[ws]+$rsquorsquomessagersquo=gtrsquoTags can only contain word charactersrsquo)

20 3 Post Management

array(rsquotagsrsquo rsquonormalizeTagsrsquo)

)

In the above we specify that the title content and status attributes are required thelength of title should not exceed 128 the status attribute value should be 1 (draft) 2(published) or 3 (archived) and the tags attribute should only contain word charactersand commas In addition we use normalizeTags to normalize the user-entered tags so thatthe tags are unique and properly separated with commas

The validators such as required length in and match are all built-in validators providedby Yii The normalizeTags validator is a method-based validator that we need to definein the Post class For more information about how to specify validation rules please referto the Guide

public function normalizeTags($attribute$params)

$this-gttags=Tagarray2string(array unique(Tagstring2array($this-gttags)))

The rules declared in this method are executed one by one when we call the validate() orsave() method of the model instance

Note It is very important to remember that attributes appearing in rules() mustbe those to be entered by end users Other attributes such as id and create time

in the Post model which are set by our code or database should not be in rules()For more details please refer to Securing Attribute Assignments

After making these changes we can visit the post creation page again to verify that thenew validation rules are taking effect

312 Customizing relations() Method

Lastly we customize the relations() method to specify the related objects of a post Bydeclaring these related objects in relations() we can exploit the powerful RelationalActiveRecord (RAR) feature to access the related object information of a post such asits author and comments without the need to write complex SQL JOIN statements

We customize the relations() method as follows

31 Customizing Post Model 21

public function relations()

return array(

rsquoauthorrsquo =gt array(selfBELONGS TO rsquoUserrsquo rsquoauthor idrsquo)

rsquocommentsrsquo =gt array(selfHAS MANY rsquoCommentrsquo rsquopost idrsquo

rsquoconditionrsquo=gtrsquocommentsstatus=rsquoCommentSTATUS APPROVED

rsquoorderrsquo=gtrsquocommentscreate time DESCrsquo)

rsquocommentCountrsquo =gt array(selfSTAT rsquoCommentrsquo rsquopost idrsquo

rsquoconditionrsquo=gtrsquostatus=rsquoCommentSTATUS APPROVED)

)

The above relations state that

bull A post belongs to an author whose class is User and the relationship is establishedbased on the author id attribute value of the post

bull A post has many comments whose class is Comment and the relationship is establishedbased on the post id attribute value of the comments These comments should besorted according to their creation time and the comments must be approved

bull The commentCount relation is a bit special as it returns back an aggregation resultwhich is about how many comments the post has

With the above relation declaration we can easily access the author and comments of apost like the following

$author=$post-gtauthor

echo $author-gtusername

$comments=$post-gtcomments

foreach($comments as $comment)

echo $comment-gtcontent

For more details about how to declare and use relations please refer to the Guide

313 Representing Status in Text

Because the status of a post is stored as an integer in the database we need to provide atextual representation so that it is more intuitive when being displayed to end users In alarge system the similar requirement is very common

22 3 Post Management

As a generic solution we use the tbl lookup table to store the mapping between integervalues and textual representations that are needed by other data objects We modify theLookup model class as follows to more easily access the textual data in the table

class Lookup extends CActiveRecord

private static $ items=array()

public static function items($type)

if(isset(self$ items[$type]))

selfloadItems($type)

return self$ items[$type]

public static function item($type$code)

if(isset(self$ items[$type]))

selfloadItems($type)

return isset(self$ items[$type][$code]) self$ items[$type][$code] false

private static function loadItems($type)

self$ items[$type]=array()

$models=selfmodel()-gtfindAll(array(

rsquoconditionrsquo=gtrsquotype=typersquo

rsquoparamsrsquo=gtarray(rsquotypersquo=gt$type)

rsquoorderrsquo=gtrsquopositionrsquo

))

foreach($models as $model)

self$ items[$type][$model-gtcode]=$model-gtname

Our new code mainly provides two static methods Lookupitems() and Lookupitem()The former returns a list of strings belonging to the specified data type while the latterreturns a particular string for the given data type and data value

Our blog database is pre-populated with two lookup types PostStatus and CommentStatusThe former refers to the possible post statuses while the latter the comment statuses

In order to make our code easier to read we also declare a set of constants to representthe status integer values We should use these constants through our code when referringto the corresponding status values

32 Creating and Updating Posts 23

class Post extends CActiveRecord

const STATUS DRAFT=0

const STATUS PUBLISHED=1

const STATUS ARCHIVED=2

Therefore we can call Lookupitems(rsquoPostStatusrsquo) to get the list of possible post statuses(text strings indexed by the corresponding integer values) and call Lookupitem(rsquoPostStatusrsquoPostSTATUS PUBLISHED) to get the string representation of the published status

32 Creating and Updating Posts

With the Post model ready we need to fine-tune the actions and views for the controllerPostController In this section we first customize the access control of CRUD operationswe then modify the code implementing the create and update operations

321 Customizing Access Control

The first thing we want to do is to customize the access control because the code generatedby yiic does not fit our needs

We modify the accessRules() method in the file wwwrootblogprotectedcontrollers

PostControllerphp as follows

public function accessRules()

return array(

array(rsquoallowrsquo allow all users to perform rsquolistrsquo and rsquoshowrsquo actions

rsquoactionsrsquo=gtarray(rsquoindexrsquo rsquoviewrsquo)

rsquousersrsquo=gtarray(rsquorsquo)

)

array(rsquoallowrsquo allow authenticated users to perform any action

rsquousersrsquo=gtarray(rsquorsquo)

)

array(rsquodenyrsquo deny all users

rsquousersrsquo=gtarray(rsquorsquo)

)

)

The above rules state that all users can access the index and view actions and authenti-cated users can access any actions including the admin action The user should be denied

24 3 Post Management

access in any other scenario Note that these rules are evaluated in the order they arelisted here The first rule matching the current context makes the access decision Forexample if the current user is the system owner who tries to visit the post creation pagethe second rule will match and it will give the access to the user

322 Customizing create and update Operations

The create and update operations are very similar They both need to display an HTMLform to collect user inputs validate them and save them into database The main differ-ence is that the update operation will pre-populate the form with the existing post datafound in the database For this reason the yiic tool generates a partial view wwwroot

blogprotectedviewspost formphp that is embedded in both the create and update

views to render the needed HTML form

We first change the formphp file so that the HTML form only collects the inputs wewant title content tags and status We use plain text fields to collect inputs for thefirst three attributes and a dropdown list to collect input for status The dropdown listoptions are the text displays of the possible post statuses

ltphp echo CHtmlactiveDropDownList($postrsquostatusrsquoLookupitems(rsquoPostStatusrsquo)) gt

In the above we call Lookupitems(rsquoPostStatusrsquo) to bring back the list of post statuses

We then modify the Post class so that it can automatically set some attributes (eg create

time author id) before a post is saved to the database We override the beforeSave()

method as follows

protected function beforeSave()

if(parentbeforeSave())

if($this-gtisNewRecord)

$this-gtcreate time=$this-gtupdate time=time()

$this-gtauthor id=Yiiapp()-gtuser-gtid

else

$this-gtupdate time=time()

return true

else

return false

33 Displaying Posts 25

When we save a post we want to update the tbl tag table to reflect the change of tagfrequencies We can do this work in the afterSave() method which is automaticallyinvoked by Yii after a post is successfully saved into the database

protected function afterSave()

parentafterSave()

Tagmodel()-gtupdateFrequency($this-gt oldTags $this-gttags)

private $ oldTags

protected function afterFind()

parentafterFind()

$this-gt oldTags=$this-gttags

In the implementation because we want to detect if the user changes the tags in case he isupdating an existing post we need to know what the old tags are For this reason we alsowrite the afterFind() method to keep the old tags in the variable oldTags The methodafterFind() is invoked automatically by Yii when an AR record is populated with thedata from database

We are not going to give details of the TagupdateFrequency() method here Interestedreaders may refer to the file wwwrootyiidemosblogprotectedmodelsTagphp

33 Displaying Posts

In our blog application a post may be displayed among a list of posts or by itself Theformer is implemented as the index operation while the latter the view operation In thissection we customize both operations to fulfill our initial requirements

331 Customizing view Operation

The view operation is implemented by the actionView() method in PostController Itsdisplay is generated by the view view with the view file wwwrootblogprotectedviews

postviewphp

Below is the relevant code implementing the view operation in PostController

public function actionView()

26 3 Post Management

$this-gtrender(rsquoviewrsquoarray(

$post=$this-gtloadModel()

$this-gtrender(rsquoviewrsquoarray(

rsquomodelrsquo=gt$post

))

))

private $ model

public function loadModel()

if($this-gt model===null)

if(isset($ GET[rsquoidrsquo]))

if(Yiiapp()-gtuser-gtisGuest)

$condition=rsquostatus=rsquoPostSTATUS PUBLISHED

rsquo OR status=rsquoPOSTSTATUS ARCHIVED

else

$condition=rsquorsquo

$this-gt model=Postmodel()-gtfindbyPk($ GET[rsquoidrsquo] $condition)

if($this-gt model===null)

throw new CHttpException(404rsquoThe requested page does not existrsquo)

return $this-gt model

Our change mainly lies in the loadModel() method In this method we query the Post

table according to the id GET parameter If the post is not found or if it is not publishedor archived (when the user is a guest) we will throw a 404 HTTP error Otherwise thepost object is returned to actionView() which in turn passes the post object to the viewscript for further display

Tip Yii captures HTTP exceptions (instances of CHttpException) and displaysthem in either predefined templates or customized error views The skeleton appli-cation generated by yiic already contains a customized error view in wwwroot

blogprotectedviewssiteerrorphp We can modify this file if we want tofurther customize the error display

The change in the view script is mainly about ajdusting the formatting and styles of thepost display We will not go into details here Interested readers may refer to wwwroot

blogprotectedviewspostviewphp

33 Displaying Posts 27

332 Customizing index Operation

Like the view operation we customize the index operation in two places the actionIndex()

method in PostController and the view file wwwrootblogprotectedviewspostindex

php We mainly need to add the support for displaying a list of posts that are associatedwith a specified tag

Below is the modified actionIndex() method in PostController

public function actionIndex()

$criteria=new CDbCriteria(array(

rsquoconditionrsquo=gtrsquostatus=rsquoPostSTATUS PUBLISHED

rsquoorderrsquo=gtrsquoupdate time DESCrsquo

rsquowithrsquo=gtrsquocommentCountrsquo

))

if(isset($ GET[rsquotagrsquo]))

$criteria-gtaddSearchCondition(rsquotagsrsquo$ GET[rsquotagrsquo])

$dataProvider=new CActiveDataProvider(rsquoPostrsquo array(

rsquopaginationrsquo=gtarray(

rsquopageSizersquo=gtYiiapp()-gtparams[rsquopostsPerPagersquo]

)

rsquocriteriarsquo=gt$criteria

))

$this-gtrender(rsquoindexrsquoarray(

rsquodataProviderrsquo=gt$dataProvider

))

In the above we first create a query criteria for retrieving post list The criteria statesthat only published posts should be returned and they should be sorted according to theirupdate time in descending order Because when displaying a post in the list we want toshow how many comments the post has received in the criteria we also specify to bringback commentCount which if you remember is a relation declared in Postrelations()

In case when a user wants to see posts with a specific tag we would add a search conditionto the criteria to look for the specified tag

Using the query criteria we create a data provider which mainly serves for three purposesFirst it does pagination of the data when too many results may be returned Herewe customize the pagination by setting the page size to be an application parameterpostsPerPage Second it does sorting according to the user request And finally it feedsthe paginated and sorted data to widgets or view code for presentation

28 3 Post Management

After we finish with actionIndex() we modify the index view as follows Our change ismainly about adding the h1 header when the user specifies to display posts with a tag

ltphp if(empty($ GET[rsquotagrsquo])) gt

lth1gtPosts Tagged with ltigtltphp echo CHtmlencode($ GET[rsquotagrsquo]) gtltigtlth1gt

ltphp endif gt

ltphp $this-gtwidget(rsquoziiwidgetsCListViewrsquo array(

rsquodataProviderrsquo=gt$dataProvider

rsquoitemViewrsquo=gtrsquo viewrsquo

rsquotemplatersquo=gtitemsnpager)) gt

Note that in the above we use [CListView] to display the post list This widget requires apartial view to display the detail of each individual post Here we specify the partial viewto be view which means the file wwwrootblogprotectedviewspost viewphp In thisview script we can acccess the post instance being displayed via a local variable named$data

34 Managing Posts

Managing posts mainly refers to listing posts in an administrative view that allows us tosee posts with all statuses updating them and deleting them They are accomplished bythe admin operation and the delete operation respectively The code generated by yiic

does not need much modification Below we mainly explain how these two operations areimplemented

341 Listing Posts in Tabular View

The admin operation shows posts with all statuses in a tabular view The view supportssorting and pagination The following is the actionAdmin() method in PostController

public function actionAdmin()

$dataProvider=new CActiveDataProvider(rsquoPostrsquo array(

rsquosortrsquo=gtarray(

rsquodefaultOrderrsquo=gtrsquostatus update time DESCrsquo

)

))

$this-gtrender(rsquoadminrsquoarray(

rsquodataProviderrsquo=gt$dataProvider

))

34 Managing Posts 29

The above code is very similar to that in actionIndex() The main difference is in thequery criteria Here we do not specify any particular query criteria since we want to seeall posts We do customize the sort property of the data provider so that by default theposts are shown in the order of their statuses and update time

Below is the code for the admin view

lth1gtManage Postslth1gt

ltphp $this-gtwidget(rsquoziiwidgetsgridCGridViewrsquo array(

rsquodataProviderrsquo=gt$dataProvider

rsquocolumnsrsquo=gtarray(

array(

rsquonamersquo=gtrsquotitlersquo

rsquotypersquo=gtrsquorawrsquo

rsquovaluersquo=gtrsquoCHtmllink(CHtmlencode($data-gttitle) $data-gturl)rsquo

)

array(

rsquonamersquo=gtrsquostatusrsquo

rsquovaluersquo=gtrsquoLookupitem(PostStatus$data-gtstatus)rsquo

)

rsquocreate timedatetimersquo

rsquoupdate timedatetimersquo

array(

rsquoclassrsquo=gtrsquoCButtonColumnrsquo

)

)

)) gt

We use [CGridView] to display the posts It allows us to sort by a column and paginatethrough the posts if there are too many to be displayed in a single page Our change ismainly about how to display each column For example for the title column we specifythat it should be displayed as a hyperlink that points to the detailed view of the post

Tip When displaying text we call CHtmlencode() to encode HTML entities init This prevents from cross-site scripting attack

342 Deleting Posts

In the admin data grid there is a delete button in each row Clicking on the button shoulddelete the corresponding post Internally this triggers the delete action implemented asfollows

30 3 Post Management

public function actionDelete()

if(Yiiapp()-gtrequest-gtisPostRequest)

we only allow deletion via POST request

$this-gtloadModel()-gtdelete()

if(isset($ POST[rsquoajaxrsquo]))

$this-gtredirect(array(rsquoindexrsquo))

else

throw new CHttpException(400rsquoInvalid request Please do not repeat this request againrsquo)

The above code is the one generated by the yiic tool without any change We wouldlike to explain a little bit more about the checking on $ POST[rsquoajaxrsquo] The [CGridView]widget has a very nice feature that its sorting pagination and deletion operations are alldone in AJAX mode by default That means the whole page does not get reloaded if anyof the above operations is performed However it is also possible that the widget runs innon-AJAX mode (by setting its ajaxUpdate property to be false or disabling JavaScript onthe client side) It is necessary for the delete action to differentiate these two scenarios ifthe delete request is made via AJAX we should not redirect the user browser otherwisewe should

Deleting a post should also cause the deletion of all comments for that post In additionwe should also update the tbl tag table regarding the tags for the deleted post Both ofthese tasks can be achieved by writing an afterDelete method in the Post model class asfollows

protected function afterDelete()

parentafterDelete()

Commentmodel()-gtdeleteAll(rsquopost id=rsquo$this-gtid)

Tagmodel()-gtupdateFrequency($this-gttags rsquorsquo)

The above code is very straightforward it first deletes all those comments whose post id

is the same as the ID of the deleted post it then updates the tbl tag table for the tags

of the deleted post

34 Managing Posts 31

Tip We have to explicitly delete all comments for the deleted post here becauseSQLite does not really support foreign key constraints In a DBMS that supportsthis constraint (such as MySQL PostgreSQL) the foreign key constraint can be setup such that the DBMS automatically deletes the related comments if the post isdeleted In that case we no longer this explicit deletion call in our code

32 3 Post Management

Chapter 4

Comment Management

41 Customizing Comment Model

For the Comment model we mainly need to customize the rules() and attributeLabels()

methods The attributeLabels() method returns a mapping between attribute namesand attribute labels We do not need to touch relations() since the code generated bythe yiic tool is good enough

411 Customizing rules() Method

We first customize the validation rules generated by the yiic tool The following rules areused for comments

public function rules()

return array(

array(rsquocontent author emailrsquo rsquorequiredrsquo)

array(rsquoauthor email urlrsquo rsquolengthrsquo rsquomaxrsquo=gt128)

array(rsquoemailrsquorsquoemailrsquo)

array(rsquourlrsquorsquourlrsquo)

)

In the above we specify that the author email and content attributes are required thelength of author email and url cannot exceed 128 the email attribute must be a validemail address and the url attribute must be a valid URL

412 Customizing attributeLabels() Method

We then customize the attributeLabels() method to declare the label display for eachmodel attribute This method returns an array consisting of name-label pairs When wecall CHtmlactiveLabel() to display an attribute label

34 4 Comment Management

public function attributeLabels()

return array(

rsquoidrsquo =gt rsquoIdrsquo

rsquocontentrsquo =gt rsquoCommentrsquo

rsquostatusrsquo =gt rsquoStatusrsquo

rsquocreate timersquo =gt rsquoCreate Timersquo

rsquoauthorrsquo =gt rsquoNamersquo

rsquoemailrsquo =gt rsquoEmailrsquo

rsquourlrsquo =gt rsquoWebsitersquo

rsquopost idrsquo =gt rsquoPostrsquo

)

Tip If the label for an attribute is not declared in attributeLabels() an algo-rithm will be used to generate an appropriate label For example a label VerifyCode will be generated for attributes verify code or verifyCode

413 Customizing Saving Process

Because we want to record the creation time of a comment we override the beforeSave()

method of Comment like we do for the Post model

protected function beforeSave()

if(parentbeforeSave())

if($this-gtisNewRecord)

$this-gtcreate time=time()

return true

else

return false

42 Creating and Displaying Comments

In this section we implement the comment display and creation features

421 Displaying Comments

Instead of displaying and creating comments on individual pages we use the post detailpage (generated by the view action of PostController) Below the post content display

42 Creating and Displaying Comments 35

we display first a list of comments belonging to that post and then a comment creationform

In order to display comments on the post detail page we modify the view script wwwrootblogprotectedviewspostviewphp as follows

ltdiv id=commentsgt

ltphp if($model-gtcommentCountgt=1) gt

lth3gt

ltphp echo $model-gtcommentCount rsquocomment(s)rsquo gt

lth3gt

ltphp $this-gtrenderPartial(rsquo commentsrsquoarray(

rsquopostrsquo=gt$model

rsquocommentsrsquo=gt$model-gtcomments

)) gt

ltphp endif gt

ltdivgt

In the above we call renderPartial() to render a partial view named comments to displaythe list of comments belonging to the current post Note that in the view we use theexpression $model-gtcomments to retrieve the comments for the post This is valid becausewe have declared a comments relation in the Post class Evaluating this expression wouldtrigger an implicit JOIN database query to bring back the proper comments This featureis known as lazy relational query

The partial view comments is not very interesting It mainly goes through every commentand displays the detail of it Interested readers may refer to wwwrootyiidemosblog

protectedpost commentsphp

422 Creating Comments

To handle comment creation we first modify the actionView() method of PostController

as follows

public function actionView()

$post=$this-gtloadModel()

$comment=$this-gtnewComment($post)

$this-gtrender(rsquoviewrsquoarray(

rsquomodelrsquo=gt$post

rsquocommentrsquo=gt$comment

))

36 4 Comment Management

protected function newComment($post)

$comment=new Comment

if(isset($ POST[rsquoCommentrsquo]))

$comment-gtattributes=$ POST[rsquoCommentrsquo]

if($post-gtaddComment($comment))

if($comment-gtstatus==CommentSTATUS PENDING)

Yiiapp()-gtuser-gtsetFlash(rsquocommentSubmittedrsquorsquoThank yoursquo)

$this-gtrefresh()

return $comment

In the above we call the newComment() method before we render view In the newComment()

method we generate a Comment instance and check if the comment form is submitted If sowe try to add the comment for the post by calling $post-gtaddComment($comment) If it goesthrough we refresh the post detail page In case the comment needs to be approved we willshow a flash message to indicate this decision A flash message is usually a confirmationmessage displayed to end users If the user clicks on the refresh button of his browser themessage will disappear

We also need to modify wwwrootblogprotectedviewspostviewphp furthermore

ltdiv id=commentsgt

lth3gtLeave a Commentlth3gt

ltphp if(Yiiapp()-gtuser-gthasFlash(rsquocommentSubmittedrsquo)) gt

ltdiv class=successgt

ltphp echo Yiiapp()-gtuser-gtgetFlash(rsquocommentSubmittedrsquo) gt

ltdivgt

ltphp else gt

ltphp $this-gtrenderPartial(rsquocomment formrsquoarray(

rsquomodelrsquo=gt$comment

)) gt

ltphp endif gt

ltdivgtlt-- comments --gt

43 Managing Comments 37

In the above code we display the flash message if it is available If not we displaythe comment input form by rendering the partial view wwwrootblogprotectedviews

comment formphp

43 Managing Comments

Comment management includes updating deleting and approving comments These op-erations are implemented as actions in the CommentController class

431 Updating and Deleting Comments

The code generated by yiic for updating and deleting comments remains largely un-changed

432 Approving Comments

When comments are newly created they are in pending approval status and need to beapproved in order to be visible to guest users Approving a comment is mainly aboutchanging the status column of the comment

We create an actionApprove() method in CommentController as follows

public function actionApprove()

if(Yiiapp()-gtrequest-gtisPostRequest)

$comment=$this-gtloadModel()

$comment-gtapprove()

$this-gtredirect(array(rsquoindexrsquo))

else

throw new CHttpException(400rsquoInvalid requestrsquo)

In the above when the approve action is invoked via a POST request we call the approve()

method defined in the Comment model to change the status We then redirect the userbrowser to the page displaying the post that this comment belongs to

We also modify the actionIndex() method of Comment to show all comments We wouldlike to see comments pending approved to show up first

public function actionIndex()

38 4 Comment Management

$dataProvider=new CActiveDataProvider(rsquoCommentrsquo array(

rsquocriteriarsquo=gtarray(

rsquowithrsquo=gtrsquopostrsquo

rsquoorderrsquo=gtrsquotstatus tcreate time DESCrsquo

)

))

$this-gtrender(rsquoindexrsquoarray(

rsquodataProviderrsquo=gt$dataProvider

))

Notice that in the above code because both tbl post and tbl comment have columns statusand create time we need to disambiguate the corresponding column reference by prefixingthem with table alias names As described in the guide the alias for the primary table ina relational query is always t Therefore we are prefixing t to the status and create time

columns in the above code

Like the post index view the index view for CommentController uses [CListView] to displaythe comment list which in turn uses the partial view wwwrootblogprotectedviews

comment viewphp to display the detail of each individual comment We will not go intodetails here Interested readers may refer to the corresponding file in the blog demowwwrootyiidemosblogprotectedviewscomment viewphp

Chapter 5

Portlets

51 Customizing Page Layout

Features like rdquothe most recent commentsrdquo rdquotag cloudrdquo are better to be implemented inportlets A portlet is a pluggable user interface component that renders a fragment ofHTML code In this section we describe how to set up the portlet architecture for ourblog application

Before we go into details about developing portlets we first need to adjust our page layoutso that the portlets can be placed in a side bar

A page layout determines how to share common sections (eg header footer) amongdifferent pages and how to embed contents that are specific for different pages In Yiia page layout is represented as a view under the protectedviewslayouts folder Whenwe call the render() method in a controller the rendering result will be automaticallyembedded into this layout view before it is displayed to end users The default layoutview is named as main which means the wwwrootblogprotectedviewslayoutsmain

php file in our blog application

We could directly modify the main layout to add a side bar However doing so wouldmake some irrelevant pages such as about contact pages to have a side bar which is notalways desirable For this reason we need different layouts for different pages

We thus create two different layouts column1 and column2 The former displays the pagein a single column while the latter displays two columns in the page one of which is theside bar We will use column2 layout for the post and comment pages and column1 forthe rest of the pages Because both column1 and column2 still share the same header andfooter sections we let them to share the main layout

Based on the above description we create the column1 layout as follows

ltphp $this-gtbeginContent(rsquolayoutsmainrsquo) gt

ltdiv class=containergt

40 5 Portlets

ltdiv id=contentgt

ltphp echo $content gt

ltdivgtlt-- content --gt

ltdivgt

ltphp $this-gtendContent() gt

and the column2 layout as follows

ltphp $this-gtbeginContent(rsquolayoutsmainrsquo) gt

ltdiv class=containergt

ltdiv class=span-18gt

ltdiv id=contentgt

ltphp echo $content gt

ltdivgtlt-- content --gt

ltdivgt

ltdiv class=span-6 lastgt

ltdiv id=sidebargt

portlets to be placed here

ltdivgtlt-- sidebar --gt

ltdivgt

ltdivgt

ltphp $this-gtendContent() gt

As we can see both column1 and column2 are enclosed in a pair of beginContent andendContent calls They essentially use the specified view layoutsmain to decorate theenclosed content In all these layout views the variable $content refers to the dynamiccontent to be embedded into the layout

Besides customizing the layout view file we also need to adjust the CSS file wwwroot

blogcssmaincss so that the overall appearance would look like what we see in the blogdemo We will not go into details here

Info The skeleton application generated by the yiic tool uses the BluePrint CSSframework It provides a nice font reset and a robust grid system For more detailson how to use this framework please refer to its documentation

After creating the new layouts we need to set the layout property of controllers to makeuse of them So far we have three controllers SiteController PostController andCommentController We set SiteController to use column1 as the default layout whilethe rest two of the controllers use column2 for example

class PostController extends Controller

52 Creating User Menu Portlet 41

public $layout=rsquocolumn2rsquo

Tip If some view of the controller does not use the same layout as declaredby the layout property it may customize it in the view by a simple assignment$this-gtlayout=rsquonewlayoutrsquo

52 Creating User Menu Portlet

Based on the requirements analysis we need three different portlets the rdquouser menurdquoportlet the rdquotag cloudrdquo portlet and the rdquorecent commentsrdquo portlet We will develop theseportlets by extending the [CPortlet] widget provided by Yii

In this section we will develop our first concrete portlet - the user menu portlet whichdisplays a list of menu items that are only available to authenticated users The menucontains four items

bull Approve Comments a hyperlink that leads to a list of comments pending approval

bull Create New Post a hyperlink that leads to the post creation page

bull Manage Posts a hyperlink that leads to the post management page

bull Logout a link button that would log out the current user

521 Creating UserMenu Class

We create the UserMenu class to represent the logic part of the user menu portlet Theclass is saved in the file wwwrootblogprotectedcomponentsUserMenuphp which has thefollowing content

Yiiimport(rsquoziiwidgetsCPortletrsquo)

class UserMenu extends CPortlet

public function init()

$this-gttitle=CHtmlencode(Yiiapp()-gtuser-gtname)

parentinit()

42 5 Portlets

protected function renderContent()

$this-gtrender(rsquouserMenursquo)

The UserMenu class extends from the CPortlet class that we created previously It overridesboth the init() method and the renderContent() method of CPortlet The former setsthe portlet title to be the name of the current user the latter generates the portlet bodycontent by rendering a view named userMenu

Tip Notice that we have to explicitly include the CPortlet class by calling Yii

import() before we refer to it the first time This is because CPortlet is part of thezii project ndash the official extension library for Yii For performance considerationclasses in this project are not listed as core classes Therefore we have to import itbefore we use it the first time

522 Creating userMenu View

Next we create the userMenu view which is saved in the file wwwrootblogprotected

componentsviewsuserMenuphp

ltulgt

ltligtltphp echo CHtmllink(rsquoCreate New Postrsquoarray(rsquopostcreatersquo)) gtltligt

ltligtltphp echo CHtmllink(rsquoManage Postsrsquoarray(rsquopostadminrsquo)) gtltligt

ltligtltphp echo CHtmllink(rsquoApprove Commentsrsquoarray(rsquocommentindexrsquo))

rsquo (rsquo Commentmodel()-gtpendingCommentCount rsquo)rsquo gtltligt

ltligtltphp echo CHtmllink(rsquoLogoutrsquoarray(rsquositelogoutrsquo)) gtltligt

ltulgt

Info By default view files for a widget should be placed under the views sub-directory of the directory containing the widget class file The file name must bethe same as the view name

523 Using UserMenu Portlet

It is time for us to make use of our newly completed UserMenu portlet We modify thelayout view file wwwrootblogprotectedviewslayoutscolumn2php as follows

53 Creating Tag Cloud Portlet 43

ltdiv id=sidebargt

ltphp if(Yiiapp()-gtuser-gtisGuest) $this-gtwidget(rsquoUserMenursquo) gt

ltdivgt

In the above we call the widget() method to generate and execute an instance of theUserMenu class Because the portlet should only be displayed to authenticated users weonly call widget() when the isGuest property of the current user is false (meaning theuser is authenticated)

524 Testing UserMenu Portlet

Letrsquos test what we have so far

1 Open a browser window and enter the URL httpwwwexamplecomblogindex

php Verify that there is nothing displayed in the side bar section of the page

2 Click on the Login hyperlink and fill out the login form to login If successful verifythat the UserMenu portlet appears in the side bar and the portlet has the usernameas its title

3 Click on the rsquoLogoutrsquo hyperlink in the UserMenu portlet Verify that the logout actionis successful and the UserMenu portlet disappears

525 Summary

What we have created is a portlet that is highly reusable We can easily reuse it in adifferent project with little or no modification Moreover the design of this portlet followsclosely the philosophy that logic and presentation should be separated While we did notpoint this out in the previous sections such practice is used nearly everywhere in a typicalYii application

53 Creating Tag Cloud Portlet

Tag cloud displays a list of post tags with visual decorations hinting the popularity of eachindividual tag

531 Creating TagCloud Class

We create the TagCloud class in the file wwwrootblogprotectedcomponentsTagCloud

php The file has the following content

44 5 Portlets

Yiiimport(rsquoziiwidgetsCPortletrsquo)

class TagCloud extends CPortlet

public $title=rsquoTagsrsquo

public $maxTags=20

protected function renderContent()

$tags=Tagmodel()-gtfindTagWeights($this-gtmaxTags)

foreach($tags as $tag=gt$weight)

$link=CHtmllink(CHtmlencode($tag) array(rsquopostindexrsquorsquotagrsquo=gt$tag))

echo CHtmltag(rsquospanrsquo array(

rsquoclassrsquo=gtrsquotagrsquo

rsquostylersquo=gtfont-size$weightpt) $link)n

Unlike the UserMenu portlet the TagCloud portlet does not use a view Instead its presen-tation is done in the renderContent() method This is because the presentation does notcontain much HTML tags

We display each tag as a hyperlink to the post index page with the corresponding tagparameter The font size of each tag link is adjusted according to their relative weightamong other tags If a tag has higher frequency value than the other it will have a biggerfont size

532 Using TagCloud Portlet

Usage of the TagCloud portlet is very simple We modify the layout file wwwrootblog

protectedviewslayoutsmainphp as follows

ltdiv id=sidebargt

ltphp if(Yiiapp()-gtuser-gtisGuest) $this-gtwidget(rsquoUserMenursquo) gt

ltphp $this-gtwidget(rsquoTagCloudrsquo array(

rsquomaxTagsrsquo=gtYiiapp()-gtparams[rsquotagCloudCountrsquo]

)) gt

ltdivgt

54 Creating Recent Comments Portlet 45

54 Creating Recent Comments Portlet

In this section we create the last portlet that displays a list of comments recently pub-lished

541 Creating RecentComments Class

We create the RecentComments class in the file wwwrootblogprotectedcomponentsRecentCommentsphp The file has the following content

Yiiimport(rsquoziiwidgetsCPortletrsquo)

class RecentComments extends CPortlet

public $title=rsquoRecent Commentsrsquo

public $maxComments=10

public function getRecentComments()

return Commentmodel()-gtfindRecentComments($this-gtmaxComments)

protected function renderContent()

$this-gtrender(rsquorecentCommentsrsquo)

In the above we invoke the findRecentComments method which is defined in the Comment

class as follows

class Comment extends CActiveRecord

public function findRecentComments($limit=10)

return $this-gtwith(rsquopostrsquo)-gtfindAll(array(

rsquoconditionrsquo=gtrsquotstatus=rsquoselfSTATUS APPROVED

rsquoorderrsquo=gtrsquotcreate time DESCrsquo

rsquolimitrsquo=gt$limit

))

46 5 Portlets

542 Creating recentComments View

The recentComments view is saved in the file wwwrootblogprotectedcomponentsviews

recentCommentsphp It simply displays every comment returned by the RecentComments

getRecentComments() method

543 Using RecentComments Portlet

We modify the layout file wwwrootblogprotectedviewslayoutsmainphp to embedthis last portlet

ltdiv id=sidebargt

ltphp if(Yiiapp()-gtuser-gtisGuest) $this-gtwidget(rsquoUserMenursquo) gt

ltphp $this-gtwidget(rsquoTagCloudrsquo array(

rsquomaxTagsrsquo=gtYiiapp()-gtparams[rsquotagCloudCountrsquo]

)) gt

ltphp $this-gtwidget(rsquoRecentCommentsrsquo array(

rsquomaxCommentsrsquo=gtYiiapp()-gtparams[rsquorecentCommentCountrsquo]

)) gt

ltdivgt

Chapter 6

Final Work

61 Beautifying URLs

The URLs linking various pages of our blog application currently look ugly For examplethe URL for the page showing a post looks like the following

indexphpr=postshowampid=1

In this section we describe how to beautify these URLs and make them SEO-friendlyOur goal is to be able to use the following URLs in the application

1 indexphppostsyii leads to the page showing a list of posts with tag yii

2 indexphppost2A+Test+Post leads to the page showing the detail of the postwith ID 2 whose title is A Test Post

3 indexphppostupdate1 leads to the page that allows updating the post with ID1

Note that in the second URL format we include the post title in the URL This is mainlyto make the URL SEO friendly It is said that search engines may also respect the wordsfound in a URL when it is being indexed

To achieve our goal we modify the application configuration as follows

return array(

rsquocomponentsrsquo=gtarray(

rsquourlManagerrsquo=gtarray(

rsquourlFormatrsquo=gtrsquopathrsquo

rsquorulesrsquo=gtarray(

48 6 Final Work

rsquopostltidd+gtlttitlegtrsquo=gtrsquopostviewrsquorsquopostslttaggtrsquo=gtrsquopostindexrsquo

)

)

)

)

In the above we configure the urlManager component by setting its urlFormat propertyto be path and adding a set of rules

The rules are used by urlManager to parse and create the URLs in the desired formatFor example the first rule says that if a URL indexphppostsyii is requested theurlManager component should be responsible to dispatch the request to the route post

index and generate a tag GET parameter with the value yii On the other hand whencreating a URL with the route postindex and parameter tag the urlManager componentwill also use this rule to generate the desired URL indexphppostsyii For this reasonwe say that urlManager is a two-way URL manager

The urlManager component can further beautify our URLs such as hiding indexphp inthe URLs appending suffix like html to the URLs We can obtain these features easilyby configuring various properties of urlManager in the application configuration For moredetails please refer to the Guide

62 Logging Errors

A production Web application often needs sophisticated logging for various events In ourblog application we would like to log the errors occurring when it is being used Sucherrors could be programming mistakes or usersrsquo misuse of the system Logging these errorswill help us to improve the blog application

We enable the error logging by modifying the application configuration as follows

return array(

rsquopreloadrsquo=gtarray(rsquologrsquo)

rsquocomponentsrsquo=gtarray(

rsquologrsquo=gtarray(

rsquoclassrsquo=gtrsquoCLogRouterrsquo

rsquoroutesrsquo=gtarray(

array(

rsquoclassrsquo=gtrsquoCFileLogRoutersquo

rsquolevelsrsquo=gtrsquoerror warningrsquo

63 Final Tune-up and Deployment 49

)

)

)

)

)

With the above configuration if an error or warning occurs detailed information will belogged and saved in a file located under the directory wwwrootblogprotectedruntime

The log component offers more advanced features such as sending log messages to a listof email addresses displaying log messages in JavaScript console window etc For moredetails please refer to the Guide

63 Final Tune-up and Deployment

We are close to finish our blog application Before deployment we would like to do sometune-ups

631 Changing Home Page

We change to use the post list page as the home page We modify the application config-uration as follows

return array(

rsquodefaultControllerrsquo=gtrsquopostrsquo

)

Tip Because PostController already declares list to be its default action whenwe access the home page of the application we will see the result generated by thelist action of the post controller

632 Enabling Schema Caching

Because ActiveRecord relies on the metadata about tables to determine the column infor-mation it takes time to read the metadata and analyze it This may not be a problemduring development stage but for an application running in production mode it is a totalwaste of time if the database schema does not change Therefore we should enable theschema caching by modifying the application configuration as follows

50 6 Final Work

return array(

rsquocomponentsrsquo=gtarray(

rsquocachersquo=gtarray(

rsquoclassrsquo=gtrsquoCDbCachersquo

)

rsquodbrsquo=gtarray(

rsquoclassrsquo=gtrsquosystemdbCDbConnectionrsquo

rsquoconnectionStringrsquo=gtrsquosqlitewwwrootblogprotecteddatablogdbrsquo

rsquoschemaCachingDurationrsquo=gt3600

)

)

)

In the above we first add a cache component which uses a default SQLite database as thecaching storage If our server is equipped with other caching extensions such as APCwe could change to use them as well We also modify the db component by setting itsschemaCachingDuration property to be 3600 which means the parsed database schemadata can remain valid in cache for 3600 seconds

633 Disabling Debugging Mode

We modify the entry script file wwwrootblogindexphp by removing the line definingthe constant YII DEBUG This constant is useful during development stage because it allowsYii to display more debugging information when an error occurs However when theapplication is running in production mode displaying debugging information is not agood idea because it may contain sensitive information such as where the script file islocated and the content in the file etc

634 Deploying the Application

The final deployment process manly involves copying the directory wwwrootblog to thetarget directory The following checklist shows every needed step

1 Install Yii in the target place if it is not available

2 Copy the entire directory wwwrootblog to the target place

3 Edit the entry script file indexphp by pointing the $yii variable to the new Yiibootstrap file

4 Edit the file protectedyiicphp by setting the $yiic variable to be the new Yiiyiicphp file

64 Future Enhancements 51

5 Change the permission of the directories assets and protectedruntime so that theyare writable by the Web server process

64 Future Enhancements

641 Using a Theme

Without writing any code our blog application is already themeable To use a themewe mainly need to develop the theme by writing customized view files in the theme Forexample to use a theme named classic that uses a different page layout we would createa layout view file wwwrootblogthemesclassicviewslayoutsmainphp We also needto change the application configuration to indicate our choice of the classic theme

return array(

rsquothemersquo=gtrsquoclassicrsquo

)

642 Internationalization

We may also internationalize our blog application so that its pages can be displayed indifferent languages This mainly involves efforts in two aspects

First we may create view files in different languages For example for the list pageof PostController we can create a view file wwwrootblogprotectedviewspostzh cn

listphp When the application is configured to use simplified Chinese (the language codeis zh cn) Yii will automatically use this new view file instead of the original one

Second we may create message translations for those messages generated by code Themessage translations should be saved as files under the directory wwwrootblogprotected

messages We also need to modify the code where we use text strings by enclosing themin the method call Yiit()

For more details about internationalization please refer to the Guide

643 Improving Performance with Cache

While the Yii framework itself is very efficient it is not necessarily true that an applicationwritten in Yii efficient There are several places in our blog application that we can improvethe performance For example the tag clould portlet could be one of the performancebottlenecks because it involves complex database query and PHP logic

52 6 Final Work

We can make use of the sophisticated caching feature provided by Yii to improve theperformance One of the most useful components in Yii is COutputCache which caches afragment of page display so that the underlying code generating the fragment does not needto be executed for every request For example in the layout file wwwrootblogprotected

viewslayoutsmainphp we can enclose the tag cloud portlet with COutputCache

ltphp if($this-gtbeginCache(rsquotagCloudrsquo array(rsquodurationrsquo=gt3600))) gt

ltphp $this-gtwidget(rsquoTagCloudrsquo array(

rsquomaxTagsrsquo=gtYiiapp()-gtparams[rsquotagCloudCountrsquo]

)) gt

ltphp $this-gtendCache() gt

With the above code the tag cloud display will be served from cache instead of beinggenerated on-the-fly for every request The cached content will remain valid in cache for3600 seconds

644 Adding New Features

Our blog application only has very basic functionalities To become a complete blogsystem more features are needed for example calendar portlet email notifications postcategorization archived post portlet and so on We will leave the implementation of thesefeatures to interested readers

  • Contents
  • License
  • Getting Started
    • Building a Blog System using Yii
    • Testdriving with Yii
      • Installing Yii
      • Creating Skeleton Application
      • Application Workflow
        • Requirements Analysis
        • Overall Design
          • Initial Prototyping
            • Setting Up Database
              • Creating Database
              • Establishing Database Connection
                • Scaffolding
                • Authenticating User
                • Summary
                  • Post Management
                    • Customizing Post Model
                      • Customizing rules() Method
                      • Customizing relations() Method
                      • Representing Status in Text
                        • Creating and Updating Posts
                          • Customizing Access Control
                          • Customizing create and update Operations
                            • Displaying Posts
                              • Customizing view Operation
                              • Customizing index Operation
                                • Managing Posts
                                  • Listing Posts in Tabular View
                                  • Deleting Posts
                                      • Comment Management
                                        • Customizing Comment Model
                                          • Customizing rules() Method
                                          • Customizing attributeLabels() Method
                                          • Customizing Saving Process
                                            • Creating and Displaying Comments
                                              • Displaying Comments
                                              • Creating Comments
                                                • Managing Comments
                                                  • Updating and Deleting Comments
                                                  • Approving Comments
                                                      • Portlets
                                                        • Customizing Page Layout
                                                        • Creating User Menu Portlet
                                                          • Creating UserMenu Class
                                                          • Creating userMenu View
                                                          • Using UserMenu Portlet
                                                          • Testing UserMenu Portlet
                                                          • Summary
                                                            • Creating Tag Cloud Portlet
                                                              • Creating TagCloud Class
                                                              • Using TagCloud Portlet
                                                                • Creating Recent Comments Portlet
                                                                  • Creating RecentComments Class
                                                                  • Creating recentComments View
                                                                  • Using RecentComments Portlet
                                                                      • Final Work
                                                                        • Beautifying URLs
                                                                        • Logging Errors
                                                                        • Final Tune-up and Deployment
                                                                          • Changing Home Page
                                                                          • Enabling Schema Caching
                                                                          • Disabling Debugging Mode
                                                                          • Deploying the Application
                                                                            • Future Enhancements
                                                                              • Using a Theme
                                                                              • Internationalization
                                                                              • Improving Performance with Cache
                                                                              • Adding New Features
Page 9: Yii Blog 1.1.0 Demo Tutor

2 1 Getting Started

After installing Yii open a browser window and access the URL httpwwwexample

comyiirequirementsindexphp It shows the requirement checker provided in the Yiirelease For our blog application besides the minimal requirements needed by Yii we alsoneed to enable both the pdo and pdo sqlite PHP extensions so that we can access SQLitedatabases

122 Creating Skeleton Application

We then use the yiic tool to create a skeleton application under the directory wwwroot

blog The yiic tool is a command line tool provided in the Yii release It can be used togenerate code to reduce certain repetitive coding tasks

Open a command window and execute the following command

wwwrootyiiframeworkyiic webapp wwwrootblog

Create a Web application under rsquowwwrootblogrsquo [Yes|No]y

Tip In order to use the yiic tool as shown above the CLI PHP program must beon the command search path If not the following command may be used instead

pathtophp wwwrootyiiframeworkyiicphp webapp wwwrootblog

To try out the application we just created open a Web browser and navigate to the URLhttpwwwexamplecomblogindexphp We should see that our skeleton applicationalready has four fully functional pages the homepage the about page the contact pageand the login page

In the following we briefly describe what we have in this skeleton application

Entry Script

We have an entry script file wwwrootblogindexphp which has the following content

ltphp

$yii=rsquowwwrootframeworkyiiphprsquo

$config=dirname( FILE )rsquoprotectedconfigmainphprsquo

remove the following line when in production mode

defined(rsquoYII DEBUGrsquo) or define(rsquoYII DEBUGrsquotrue)

12 Testdriving with Yii 3

require once($yii)

YiicreateWebApplication($config)-gtrun()

This is the only script that Web users can directly access The script first includes theYii bootstrap file yiiphp It then creates an application instance with the specifiedconfiguration and executes the application

Base Application Directory

We also have an application base directory wwwrootblogprotected The majority of ourcode and data will be placed under this directory and it should be protected from beingaccessed by Web users For Apache httpd Web server we place under this directory ahtaccess file with the following content

deny from all

For other Web servers please refer to the corresponding manual on how to protect adirectory from being accessed by Web users

123 Application Workflow

To help understand how Yii works we describe the main workflow in our skeleton appli-cation when a user is accessing its contact page

1 The user requests the URL httpwwwexamplecomblogindexphpr=sitecontact

2 The entry script is executed by the Web server to process the request

3 An application instance is created and configured with initial property values speci-fied in the application configuration file wwwrootblogprotectedconfigmainphp

4 The application resolves the request into a controller and a controller action For thecontact page request it is resolved as the site controller and the contact action (theactionContact method in wwwrootblogprotectedcontrollersSiteControllerphp)

5 The application creates the site controller in terms of a SiteController instanceand then executes it

6 The SiteController instance executes the contact action by calling its actionContact()method

4 1 Getting Started

7 The actionContact method renders a view named contact to the Web user Inter-nally this is achieved by including the view file wwwrootblogprotectedviews

sitecontactphp and embedding the result into the layout file wwwrootblog

protectedviewslayoutsmainphp

13 Requirements Analysis

The blog system that we are going to develop is a single user system The owner of thesystem will be able to perform the following actions

bull Login and logout

bull Create update and delete posts

bull Publish unpublish and archive posts

bull Approve and delete comments

All other users are guest users who can perform the following actions

bull Read posts

bull Create comments

Additional Requirements for this system include

bull The homepage of the system should display a list of the most recent posts

bull If a page contains more than 10 posts they should be displayed in pages

bull The system should display a post together with its comments

bull The system should be able to list posts with a specified tag

bull The system should show a cloud of tags indicating their use frequencies

bull The system should show a list of most recent comments

bull The system should be themeable

bull The system should use SEO-friendly URLs

14 Overall Design 5

14 Overall Design

Based on the analysis of the requirements we decide to use the following database tablesto store the persistent data for our blog application

bull tbl user stores the user information including username and password

bull tbl post stores the blog post information It mainly consists of the following columns

ndash title required title of the post

ndash content required body content of the post which uses the Markdown format

ndash status required status of the post which can be one of following values

lowast 1 meaning the post is in draft and is not visible to public

lowast 2 meaning the post is published to public

lowast 3 meaning the post is outdated and is not visible in the post list (stillaccessible individually though)

ndash tags optional a list of comma-separated words categorizing the post

bull tbl comment stores the post comment information Each comment is associated witha post and mainly consists of the following columns

ndash name required the author name

ndash email required the author email

ndash website optional the author website URL

ndash content required the comment content in plain text format

ndash status required status of the comment which indicates whether the commentis approved (value 2) or not (value 1)

bull tbl tag stores post tag frequency information that is needed to implement the tagcloud feature The table mainly contains the following columns

ndash name required the unique tag name

ndash frequency required the number of times that the tag appears in posts

bull tbl lookup stores generic lookup information It is essentially a map between integervalues and text strings The former is the data representation in our code while thelatter is the corresponding presentation to end users For example we use integer1 to represent the draft post status and string Draft to display this status to endusers This table mainly contains the following columns

6 1 Getting Started

ndash name the textual representation of the data item that is to be displayed to endusers

ndash code the integer representation of the data item

ndash type the type of the data item

ndash position the relative display order of the data item among other items of thesame type

The following entity-relation (ER) diagram shows the table structure and relationshipsabout the above tables

Figure 11 Entity-Relation Diagram of the Blog Database

Complete SQL statements corresponding to the above ER diagram may be found inthe blog demo In our Yii installation they are in the file wwwrootyiidemosblog

protecteddataschemasqlitesql

Info We name all our table names and column names in lower case This is becausedifferent DBMS often have different case-sensitivity treatment and we want to avoidtroubles like this

We also prefix all our tables with tbl This serves for two purposes First the prefixintroduces a namespace to these tables in case when they need to coexist with othertables in the same database which often happens in a shared hosting environmentwhere a single database is being used by multiple applications Second using tableprefix reduces the possibility of having some table names that are reserved keywordsin DBMS

14 Overall Design 7

We divide the development of our blog application into the following milestones

bull Milestone 1 creating a prototype of the blog system It should consist of most ofthe required functionalities

bull Milestone 2 completing post management It includes creating listing showingupdating and deleting posts

bull Milestone 3 completing comment management It includes creating listing ap-proving updating and deleting post comments

bull Milestone 4 implementing portlets It includes user menu login tag cloud andrecent comments portlets

bull Milestone 5 final tune-up and deployment

8 1 Getting Started

Chapter 2

Initial Prototyping

21 Setting Up Database

Having created a skeleton application and finished the database design in this section wewill create the blog database and establish the connection to it in the skeleton application

211 Creating Database

We choose to create a SQLite database Because the database support in Yii is built on topof PDO we can easily switch to use a different type of DBMS (eg MySQL PostgreSQL)without the need to change our application code

We create the database file blogdb under the directory wwwrootblogprotecteddataNote that both the directory and the database file have to be writable by the Web serverprocess as required by SQLite We may simply copy the database file from the blogdemo in our Yii installation which is located at wwwrootyiidemosblogprotecteddatablogdb We may also generate the database by executing the SQL statements in the filewwwrootyiidemosblogprotecteddataschemasqlitesql

Tip To execute SQL statements we may use the sqlite3 command line tool thatcan be found in the SQLite official website

212 Establishing Database Connection

To use the blog database in the skeleton application we created we need to modify itsapplication configuration which is stored in the PHP script wwwrootblogprotected

configmainphp The script returns an associative array consisting of name-value pairseach of which is used to initialize a writable property of the application instance

We configure the db component as follows

10 2 Initial Prototyping

return array(

rsquocomponentsrsquo=gtarray(

rsquodbrsquo=gtarray(

rsquoconnectionStringrsquo=gtrsquosqlitewwwrootblogprotecteddatablogdbrsquo

rsquotablePrefixrsquo=gtrsquotbl rsquo

)

)

)

The above configuration says that we have a db application component whose connectionStringproperty should be initialized as sqlitewwwrootblogprotecteddatablogdb and whosetablePrefix property should be tbl

With this configuration we can access the DB connection object using Yiiapp()-gtdb

at any place in our code Note that Yiiapp() returns the application instance that wecreate in the entry script If you are interested in possible methods and properties that theDB connection has you may refer to its class reference However in most cases we are notgoing to use this DB connection directly Instead we will use the so-called ActiveRecordto access the database

We would like to explain a bit more about the tablePrefix property that we set in theconfiguration This tells the db connection that it should respect the fact we are usingtbl as the prefix to our database table names In particular if in a SQL statement thereis a token enclosed within double curly brackets (eg post then the db connectionshould translate it into a name with the table prefix (eg tbl post) before sending it toDBMS for execution This feature is especially useful if in future we need to modify thetable name prefix without touching our source code For example if we are developing ageneric content management system (CMS) we may exploit this feature so that when itis being installed in a new environment we can allow users to choose a table prefix theylike

22 Scaffolding 11

Tip If you want to use MySQL instead of SQLite to store data you may cre-ate a MySQL database named blog using the SQL statements in wwwrootyii

demosblogprotecteddataschemamysqlsql Then modify the applicationconfiguration as follows

return array(

rsquocomponentsrsquo=gtarray(

rsquodbrsquo=gtarray(

rsquoconnectionStringrsquo =gt rsquomysqlhost=localhostdbname=blogrsquo

rsquoemulatePreparersquo =gt true

rsquousernamersquo =gt rsquorootrsquo

rsquopasswordrsquo =gt rsquorsquo

rsquocharsetrsquo =gt rsquoutf8rsquo

rsquotablePrefixrsquo =gt rsquotbl rsquo

)

)

)

22 Scaffolding

Create read update and delete (CRUD) are the four basic operations of data objects in anapplication Because the task of implementing the CRUD operations is so common whendeveloping Web applications Yii provides a tool to automate this process (also known asscaffolding) In this section we will describe how to use the tool to implement CRUD forposts and comments

Open a command window and run the following commands

wwwrootyiiframeworkyiic shell wwwrootblogindexphp

Yii Interactive Tool v11

Please type rsquohelprsquo for help Type rsquoexitrsquo to quit

gtgt model

gtgt crud Post

gtgt crud Comment

gtgt exit

12 2 Initial Prototyping

Info Some PHP installations may use a different phpini file for command line(CLI) PHP parser As a result when running the above yiic commands youmay encounter errors like rdquoYiiBaseinclude(PDOphp) failed to open streamrdquoor rdquocould not find driverrdquo Please double check your CLI PHP configuration byexecuting the following command

php -r phpinfo()

The result of the above command will show which phpini file is being used andwhich extensions are loaded If a wrong phpini file is used you may use thefollowing command to explicitly specify the correct phpini to use

php -c phpini wwwrootyiiframeworkyiicphp shell wwwrootblogindexphp

The commands above accomplish two tasks First the model command generates a modelclass file for each table in the blog database Second the crud commands generate thecode needed by the CRUD operations for the Post and Comment models

Tip The command model generates a model class for every table in the databaseSometimes we probably donrsquot want to do so (eg the database contains some irrel-evant tables) In this case we can create model classes one by one For exampleto create the User model we can use the command model User The model com-mand also has some more advanced usages For more details execute help model

command

We can test the generated code by accessing the following URLs

httpwwwexamplecomblogindexphpr=post

httpwwwexamplecomblogindexphpr=comment

Notice that the post and comment features implemented by the generated code are com-pletely independent of each other Also when creating a new post or comment we arerequired to enter information such as author id and create time which in real applica-tion should be set by the program Donrsquot worry We will fix these problems in the nextmilestones For now we should be fairly satisfied as this prototype already contains mostfeatures that we need to implement for the blog application

To prepare for the next milestones letrsquos take a closer look at the files generated by theabove commands All the files are generated under wwwrootblogprotected For conve-nience we group them into model files controller files and view files

22 Scaffolding 13

bull model files

ndash modelsUserphp contains the User class that extends from CActiveRecord andcan be used to access the tbl user database table

ndash modelsPostphp contains the Post class that extends from CActiveRecord andcan be used to access the tbl post database table

ndash modelsTagphp contains the Tag class that extends from CActiveRecord andcan be used to access the tbl tag database table

ndash modelsCommentphp contains the Comment class that extends from CActiveRe-cord and can be used to access the tbl comment database table

ndash modelsLookupphp contains the Lookup class that extends from CActiveRecordand can be used to access the tbl lookup database table

bull controller file

ndash controllersPostControllerphp contains the PostController class which is thecontroller in charge of all CRUD operations about posts

ndash controllersCommentControllerphp contains the CommentController class whichis the controller in charge of all CRUD operations about comments

bull view files

ndash viewspostcreatephp is the view file that shows an HTML form to create anew post

ndash viewspostupdatephp is the view file that shows an HTML form to update anexisting post

ndash viewspostviewphp is the view file that displays the detailed information of apost

ndash viewspostindexphp is the view file that displays a list of posts

ndash viewspostadminphp is the view file that displays posts in a table with ad-ministrative commands

ndash viewspost formphp is the partial view file embedded in viewspostcreate

php and viewspostupdatephp It displays the HTML form for collecting postinformation

ndash viewspost viewphp is the partial view file used by viewspostindexphp Itdisplays the brief view of a single post

ndash a similar set of view files are also generated for comment

14 2 Initial Prototyping

In order to understand better how the above files are used we show in the following theworkflow that occurs in the blog application when displaying a list of posts

1 The user requests the URL httpwwwexamplecomblogindexphpr=posts

2 The entry script is executed by the Web server which creates and initializes anapplication instance to handle the request

3 The application creates an instance of PostController and executes it

4 The PostController instance executes the index action by calling its actionIndex()

method Note that index is the default action if the user does not specify an actionto execute in the URL

5 The actionIndex() method queries database to bring back the list of recent posts

6 The actionIndex() method renders the index view with the post data

23 Authenticating User

Our blog application needs to differentiate between the system owner and guest usersTherefore we need to implement the user authentication feature

As you may have found that the skeleton application already provides user authenticationby checking if the username and password are both demo or admin In this section wewill modify the corresponding code so that the authentication is done against the User

database table

User authentication is performed in a class implementing the IUserIdentity interface Theskeleton application uses the UserIdentity class for this purpose The class is stored inthe file wwwrootblogprotectedcomponentsUserIdentityphp

Tip By convention the name of a class file must be the same as the correspondingclass name suffixed with the extension php Following this convention one canrefer to a class using a path alias For example we can refer to the UserIdentity

class with the alias applicationcomponentsUserIdentity Many APIs in Yiican recognize path aliases (eg YiicreateComponent()) and using path aliasesavoids the necessity of embedding absolute file paths in the code The existence ofthe latter often causes trouble when we deploy an application

We modify the UserIdentity class as follows

23 Authenticating User 15

ltphp

class UserIdentity extends CUserIdentity

private $ id

public function authenticate()

$username=strtolower($this-gtusername)

$user=Usermodel()-gtfind(rsquoLOWER(username)=rsquoarray($username))

if($user===null)

$this-gterrorCode=selfERROR USERNAME INVALID

else if($user-gtvalidatePassword($this-gtpassword))

$this-gterrorCode=selfERROR PASSWORD INVALID

else

$this-gt id=$user-gtid

$this-gtusername=$user-gtusername

$this-gterrorCode=selfERROR NONE

return $this-gterrorCode==selfERROR NONE

public function getId()

return $this-gt id

In the authenticate() method we use the User class to look for a row in the User tablewhose username column is the same as the given username in a case-insensitive mannerRemember that the User class was created using the yiic tool in the prior section Becausethe User class extends from CActiveRecord we can exploit the ActiveRecord feature toaccess the User table in an OOP fashion

In order to check if the user has entered a valid password we invoke the validatePassword

method of the User class We need to modify the file wwwrootblogprotectedmodels

Userphp as follows Note that instead of storing the plain password in the database westore the hash result of the password and a randomly generated salt key When validatingthe user-entered password we should compare the hash results instead

class User extends CActiveRecord

public function validatePassword($password)

return $this-gthashPassword($password$this-gtsalt)===$this-gtpassword

16 2 Initial Prototyping

public function hashPassword($password$salt)

return md5($salt$password)

In the UserIdentity class we also override the getId() method which returns the id valueof the user found in the User table The parent implementation would return the usernameinstead Both the username and id properties will be stored in the user session and maybe accessed via Yiiapp()-gtuser from anywhere in our code

Tip In the UserIdentity class we reference the class CUserIdentity without ex-plicitly including the corresponding class file This is because CUserIdentity is acore class provided by the Yii framework Yii will automatically include the classfile for any core class when it is referenced for the first time

We also do the same with the User class This is because the User class file is placedunder the directory wwwrootblogprotectedmodels which has been added tothe PHP include path according to the following lines found in the applicationconfiguration

return array(

rsquoimportrsquo=gtarray(

rsquoapplicationmodelsrsquo

rsquoapplicationcomponentsrsquo

)

)

The above configuration says that any class whose class file is located under either wwwrootblogprotectedmodels or wwwrootblogprotectedcomponents willbe automatically included when the class is referenced for the first time

The UserIdentity class is mainly used by the LoginForm class to authenticate a user basedon the username and password input collected from the login page The following codefragment shows how UserIdentity is used

$identity=new UserIdentity($username$password)

$identity-gtauthenticate()

switch($identity-gterrorCode)

24 Summary 17

case UserIdentityERROR NONE

Yiiapp()-gtuser-gtlogin($identity)

break

Info People often get confused about identity and the user application componentThe former represents a way of performing authentication while the latter is usedto represent the information related with the current user An application can onlyhave one user component but it can have one or several identity classes dependingon what kind of authentication it supports Once authenticated an identity instancemay pass its state information to the user component so that they are globallyaccessible via user

To test the modified UserIdentity class we can browse the URL httpwwwexample

comblogindexphp and try logging in with the username and password that we store inthe User table If we use the database provided by the blog demo we should be able tologin with username demo and password demo Note that this blog system does not providethe user management feature As a result a user cannot change his account or create anew one through the Web interface The user management feature may be considered asa future enhancement to the blog application

24 Summary

We have completed the milestone 1 Letrsquos summarize what we have done so far

1 We identified the requirements to be fulfilled

2 We installed the Yii framework

3 We created a skeleton application

4 We designed and created the blog database

5 We modified the application configuration by adding the database connection

6 We generated the code that implements the basic CRUD operations for both postsand comments

7 We modified the authentication method to check against the tbl user table

18 2 Initial Prototyping

For a new project most of the time will be spent in step 1 and 4 for this first milestone

Although the code generated by the yiic tool implements fully functional CRUD oper-ations for a database table it often needs to be modified in practical applications Forthis reason in the next two milestone our job is to customize the generated CRUD codeabout posts and comments so that it reaches our initial requirements

In general we first modify the model class file by adding appropriate validation rules anddeclaring relational objects We then modify the controller action and view code for eachindividual CRUD operation

Chapter 3

Post Management

31 Customizing Post Model

The Post model class generated by the yiic tool mainly needs to be modified in two places

bull the rules() method specifies the validation rules for the model attributes

bull the relations() method specifies the related objects

Info A model consists of a list of attributes each associated with a column in thecorresponding database table Attributes can be declared explicitly as class membervariables or implicitly without any declaration

311 Customizing rules() Method

We first specify the validation rules which ensure the attribute values entered by usersare correct before they are saved to the database For example the status attribute ofPost should be an integer 1 2 or 3 The yiic tool also generates validation rules for eachmodel However these rules are based on the table column information and may not beappropriate

Based on the requirement analysis we modify the rules() method as follows

public function rules()

return array(

array(rsquotitle content statusrsquo rsquorequiredrsquo)

array(rsquotitlersquo rsquolengthrsquo rsquomaxrsquo=gt128)

array(rsquostatusrsquo rsquoinrsquo rsquorangersquo=gtarray(123))

array(rsquotagsrsquo rsquomatchrsquo rsquopatternrsquo=gtrsquo^[ws]+$rsquorsquomessagersquo=gtrsquoTags can only contain word charactersrsquo)

20 3 Post Management

array(rsquotagsrsquo rsquonormalizeTagsrsquo)

)

In the above we specify that the title content and status attributes are required thelength of title should not exceed 128 the status attribute value should be 1 (draft) 2(published) or 3 (archived) and the tags attribute should only contain word charactersand commas In addition we use normalizeTags to normalize the user-entered tags so thatthe tags are unique and properly separated with commas

The validators such as required length in and match are all built-in validators providedby Yii The normalizeTags validator is a method-based validator that we need to definein the Post class For more information about how to specify validation rules please referto the Guide

public function normalizeTags($attribute$params)

$this-gttags=Tagarray2string(array unique(Tagstring2array($this-gttags)))

The rules declared in this method are executed one by one when we call the validate() orsave() method of the model instance

Note It is very important to remember that attributes appearing in rules() mustbe those to be entered by end users Other attributes such as id and create time

in the Post model which are set by our code or database should not be in rules()For more details please refer to Securing Attribute Assignments

After making these changes we can visit the post creation page again to verify that thenew validation rules are taking effect

312 Customizing relations() Method

Lastly we customize the relations() method to specify the related objects of a post Bydeclaring these related objects in relations() we can exploit the powerful RelationalActiveRecord (RAR) feature to access the related object information of a post such asits author and comments without the need to write complex SQL JOIN statements

We customize the relations() method as follows

31 Customizing Post Model 21

public function relations()

return array(

rsquoauthorrsquo =gt array(selfBELONGS TO rsquoUserrsquo rsquoauthor idrsquo)

rsquocommentsrsquo =gt array(selfHAS MANY rsquoCommentrsquo rsquopost idrsquo

rsquoconditionrsquo=gtrsquocommentsstatus=rsquoCommentSTATUS APPROVED

rsquoorderrsquo=gtrsquocommentscreate time DESCrsquo)

rsquocommentCountrsquo =gt array(selfSTAT rsquoCommentrsquo rsquopost idrsquo

rsquoconditionrsquo=gtrsquostatus=rsquoCommentSTATUS APPROVED)

)

The above relations state that

bull A post belongs to an author whose class is User and the relationship is establishedbased on the author id attribute value of the post

bull A post has many comments whose class is Comment and the relationship is establishedbased on the post id attribute value of the comments These comments should besorted according to their creation time and the comments must be approved

bull The commentCount relation is a bit special as it returns back an aggregation resultwhich is about how many comments the post has

With the above relation declaration we can easily access the author and comments of apost like the following

$author=$post-gtauthor

echo $author-gtusername

$comments=$post-gtcomments

foreach($comments as $comment)

echo $comment-gtcontent

For more details about how to declare and use relations please refer to the Guide

313 Representing Status in Text

Because the status of a post is stored as an integer in the database we need to provide atextual representation so that it is more intuitive when being displayed to end users In alarge system the similar requirement is very common

22 3 Post Management

As a generic solution we use the tbl lookup table to store the mapping between integervalues and textual representations that are needed by other data objects We modify theLookup model class as follows to more easily access the textual data in the table

class Lookup extends CActiveRecord

private static $ items=array()

public static function items($type)

if(isset(self$ items[$type]))

selfloadItems($type)

return self$ items[$type]

public static function item($type$code)

if(isset(self$ items[$type]))

selfloadItems($type)

return isset(self$ items[$type][$code]) self$ items[$type][$code] false

private static function loadItems($type)

self$ items[$type]=array()

$models=selfmodel()-gtfindAll(array(

rsquoconditionrsquo=gtrsquotype=typersquo

rsquoparamsrsquo=gtarray(rsquotypersquo=gt$type)

rsquoorderrsquo=gtrsquopositionrsquo

))

foreach($models as $model)

self$ items[$type][$model-gtcode]=$model-gtname

Our new code mainly provides two static methods Lookupitems() and Lookupitem()The former returns a list of strings belonging to the specified data type while the latterreturns a particular string for the given data type and data value

Our blog database is pre-populated with two lookup types PostStatus and CommentStatusThe former refers to the possible post statuses while the latter the comment statuses

In order to make our code easier to read we also declare a set of constants to representthe status integer values We should use these constants through our code when referringto the corresponding status values

32 Creating and Updating Posts 23

class Post extends CActiveRecord

const STATUS DRAFT=0

const STATUS PUBLISHED=1

const STATUS ARCHIVED=2

Therefore we can call Lookupitems(rsquoPostStatusrsquo) to get the list of possible post statuses(text strings indexed by the corresponding integer values) and call Lookupitem(rsquoPostStatusrsquoPostSTATUS PUBLISHED) to get the string representation of the published status

32 Creating and Updating Posts

With the Post model ready we need to fine-tune the actions and views for the controllerPostController In this section we first customize the access control of CRUD operationswe then modify the code implementing the create and update operations

321 Customizing Access Control

The first thing we want to do is to customize the access control because the code generatedby yiic does not fit our needs

We modify the accessRules() method in the file wwwrootblogprotectedcontrollers

PostControllerphp as follows

public function accessRules()

return array(

array(rsquoallowrsquo allow all users to perform rsquolistrsquo and rsquoshowrsquo actions

rsquoactionsrsquo=gtarray(rsquoindexrsquo rsquoviewrsquo)

rsquousersrsquo=gtarray(rsquorsquo)

)

array(rsquoallowrsquo allow authenticated users to perform any action

rsquousersrsquo=gtarray(rsquorsquo)

)

array(rsquodenyrsquo deny all users

rsquousersrsquo=gtarray(rsquorsquo)

)

)

The above rules state that all users can access the index and view actions and authenti-cated users can access any actions including the admin action The user should be denied

24 3 Post Management

access in any other scenario Note that these rules are evaluated in the order they arelisted here The first rule matching the current context makes the access decision Forexample if the current user is the system owner who tries to visit the post creation pagethe second rule will match and it will give the access to the user

322 Customizing create and update Operations

The create and update operations are very similar They both need to display an HTMLform to collect user inputs validate them and save them into database The main differ-ence is that the update operation will pre-populate the form with the existing post datafound in the database For this reason the yiic tool generates a partial view wwwroot

blogprotectedviewspost formphp that is embedded in both the create and update

views to render the needed HTML form

We first change the formphp file so that the HTML form only collects the inputs wewant title content tags and status We use plain text fields to collect inputs for thefirst three attributes and a dropdown list to collect input for status The dropdown listoptions are the text displays of the possible post statuses

ltphp echo CHtmlactiveDropDownList($postrsquostatusrsquoLookupitems(rsquoPostStatusrsquo)) gt

In the above we call Lookupitems(rsquoPostStatusrsquo) to bring back the list of post statuses

We then modify the Post class so that it can automatically set some attributes (eg create

time author id) before a post is saved to the database We override the beforeSave()

method as follows

protected function beforeSave()

if(parentbeforeSave())

if($this-gtisNewRecord)

$this-gtcreate time=$this-gtupdate time=time()

$this-gtauthor id=Yiiapp()-gtuser-gtid

else

$this-gtupdate time=time()

return true

else

return false

33 Displaying Posts 25

When we save a post we want to update the tbl tag table to reflect the change of tagfrequencies We can do this work in the afterSave() method which is automaticallyinvoked by Yii after a post is successfully saved into the database

protected function afterSave()

parentafterSave()

Tagmodel()-gtupdateFrequency($this-gt oldTags $this-gttags)

private $ oldTags

protected function afterFind()

parentafterFind()

$this-gt oldTags=$this-gttags

In the implementation because we want to detect if the user changes the tags in case he isupdating an existing post we need to know what the old tags are For this reason we alsowrite the afterFind() method to keep the old tags in the variable oldTags The methodafterFind() is invoked automatically by Yii when an AR record is populated with thedata from database

We are not going to give details of the TagupdateFrequency() method here Interestedreaders may refer to the file wwwrootyiidemosblogprotectedmodelsTagphp

33 Displaying Posts

In our blog application a post may be displayed among a list of posts or by itself Theformer is implemented as the index operation while the latter the view operation In thissection we customize both operations to fulfill our initial requirements

331 Customizing view Operation

The view operation is implemented by the actionView() method in PostController Itsdisplay is generated by the view view with the view file wwwrootblogprotectedviews

postviewphp

Below is the relevant code implementing the view operation in PostController

public function actionView()

26 3 Post Management

$this-gtrender(rsquoviewrsquoarray(

$post=$this-gtloadModel()

$this-gtrender(rsquoviewrsquoarray(

rsquomodelrsquo=gt$post

))

))

private $ model

public function loadModel()

if($this-gt model===null)

if(isset($ GET[rsquoidrsquo]))

if(Yiiapp()-gtuser-gtisGuest)

$condition=rsquostatus=rsquoPostSTATUS PUBLISHED

rsquo OR status=rsquoPOSTSTATUS ARCHIVED

else

$condition=rsquorsquo

$this-gt model=Postmodel()-gtfindbyPk($ GET[rsquoidrsquo] $condition)

if($this-gt model===null)

throw new CHttpException(404rsquoThe requested page does not existrsquo)

return $this-gt model

Our change mainly lies in the loadModel() method In this method we query the Post

table according to the id GET parameter If the post is not found or if it is not publishedor archived (when the user is a guest) we will throw a 404 HTTP error Otherwise thepost object is returned to actionView() which in turn passes the post object to the viewscript for further display

Tip Yii captures HTTP exceptions (instances of CHttpException) and displaysthem in either predefined templates or customized error views The skeleton appli-cation generated by yiic already contains a customized error view in wwwroot

blogprotectedviewssiteerrorphp We can modify this file if we want tofurther customize the error display

The change in the view script is mainly about ajdusting the formatting and styles of thepost display We will not go into details here Interested readers may refer to wwwroot

blogprotectedviewspostviewphp

33 Displaying Posts 27

332 Customizing index Operation

Like the view operation we customize the index operation in two places the actionIndex()

method in PostController and the view file wwwrootblogprotectedviewspostindex

php We mainly need to add the support for displaying a list of posts that are associatedwith a specified tag

Below is the modified actionIndex() method in PostController

public function actionIndex()

$criteria=new CDbCriteria(array(

rsquoconditionrsquo=gtrsquostatus=rsquoPostSTATUS PUBLISHED

rsquoorderrsquo=gtrsquoupdate time DESCrsquo

rsquowithrsquo=gtrsquocommentCountrsquo

))

if(isset($ GET[rsquotagrsquo]))

$criteria-gtaddSearchCondition(rsquotagsrsquo$ GET[rsquotagrsquo])

$dataProvider=new CActiveDataProvider(rsquoPostrsquo array(

rsquopaginationrsquo=gtarray(

rsquopageSizersquo=gtYiiapp()-gtparams[rsquopostsPerPagersquo]

)

rsquocriteriarsquo=gt$criteria

))

$this-gtrender(rsquoindexrsquoarray(

rsquodataProviderrsquo=gt$dataProvider

))

In the above we first create a query criteria for retrieving post list The criteria statesthat only published posts should be returned and they should be sorted according to theirupdate time in descending order Because when displaying a post in the list we want toshow how many comments the post has received in the criteria we also specify to bringback commentCount which if you remember is a relation declared in Postrelations()

In case when a user wants to see posts with a specific tag we would add a search conditionto the criteria to look for the specified tag

Using the query criteria we create a data provider which mainly serves for three purposesFirst it does pagination of the data when too many results may be returned Herewe customize the pagination by setting the page size to be an application parameterpostsPerPage Second it does sorting according to the user request And finally it feedsthe paginated and sorted data to widgets or view code for presentation

28 3 Post Management

After we finish with actionIndex() we modify the index view as follows Our change ismainly about adding the h1 header when the user specifies to display posts with a tag

ltphp if(empty($ GET[rsquotagrsquo])) gt

lth1gtPosts Tagged with ltigtltphp echo CHtmlencode($ GET[rsquotagrsquo]) gtltigtlth1gt

ltphp endif gt

ltphp $this-gtwidget(rsquoziiwidgetsCListViewrsquo array(

rsquodataProviderrsquo=gt$dataProvider

rsquoitemViewrsquo=gtrsquo viewrsquo

rsquotemplatersquo=gtitemsnpager)) gt

Note that in the above we use [CListView] to display the post list This widget requires apartial view to display the detail of each individual post Here we specify the partial viewto be view which means the file wwwrootblogprotectedviewspost viewphp In thisview script we can acccess the post instance being displayed via a local variable named$data

34 Managing Posts

Managing posts mainly refers to listing posts in an administrative view that allows us tosee posts with all statuses updating them and deleting them They are accomplished bythe admin operation and the delete operation respectively The code generated by yiic

does not need much modification Below we mainly explain how these two operations areimplemented

341 Listing Posts in Tabular View

The admin operation shows posts with all statuses in a tabular view The view supportssorting and pagination The following is the actionAdmin() method in PostController

public function actionAdmin()

$dataProvider=new CActiveDataProvider(rsquoPostrsquo array(

rsquosortrsquo=gtarray(

rsquodefaultOrderrsquo=gtrsquostatus update time DESCrsquo

)

))

$this-gtrender(rsquoadminrsquoarray(

rsquodataProviderrsquo=gt$dataProvider

))

34 Managing Posts 29

The above code is very similar to that in actionIndex() The main difference is in thequery criteria Here we do not specify any particular query criteria since we want to seeall posts We do customize the sort property of the data provider so that by default theposts are shown in the order of their statuses and update time

Below is the code for the admin view

lth1gtManage Postslth1gt

ltphp $this-gtwidget(rsquoziiwidgetsgridCGridViewrsquo array(

rsquodataProviderrsquo=gt$dataProvider

rsquocolumnsrsquo=gtarray(

array(

rsquonamersquo=gtrsquotitlersquo

rsquotypersquo=gtrsquorawrsquo

rsquovaluersquo=gtrsquoCHtmllink(CHtmlencode($data-gttitle) $data-gturl)rsquo

)

array(

rsquonamersquo=gtrsquostatusrsquo

rsquovaluersquo=gtrsquoLookupitem(PostStatus$data-gtstatus)rsquo

)

rsquocreate timedatetimersquo

rsquoupdate timedatetimersquo

array(

rsquoclassrsquo=gtrsquoCButtonColumnrsquo

)

)

)) gt

We use [CGridView] to display the posts It allows us to sort by a column and paginatethrough the posts if there are too many to be displayed in a single page Our change ismainly about how to display each column For example for the title column we specifythat it should be displayed as a hyperlink that points to the detailed view of the post

Tip When displaying text we call CHtmlencode() to encode HTML entities init This prevents from cross-site scripting attack

342 Deleting Posts

In the admin data grid there is a delete button in each row Clicking on the button shoulddelete the corresponding post Internally this triggers the delete action implemented asfollows

30 3 Post Management

public function actionDelete()

if(Yiiapp()-gtrequest-gtisPostRequest)

we only allow deletion via POST request

$this-gtloadModel()-gtdelete()

if(isset($ POST[rsquoajaxrsquo]))

$this-gtredirect(array(rsquoindexrsquo))

else

throw new CHttpException(400rsquoInvalid request Please do not repeat this request againrsquo)

The above code is the one generated by the yiic tool without any change We wouldlike to explain a little bit more about the checking on $ POST[rsquoajaxrsquo] The [CGridView]widget has a very nice feature that its sorting pagination and deletion operations are alldone in AJAX mode by default That means the whole page does not get reloaded if anyof the above operations is performed However it is also possible that the widget runs innon-AJAX mode (by setting its ajaxUpdate property to be false or disabling JavaScript onthe client side) It is necessary for the delete action to differentiate these two scenarios ifthe delete request is made via AJAX we should not redirect the user browser otherwisewe should

Deleting a post should also cause the deletion of all comments for that post In additionwe should also update the tbl tag table regarding the tags for the deleted post Both ofthese tasks can be achieved by writing an afterDelete method in the Post model class asfollows

protected function afterDelete()

parentafterDelete()

Commentmodel()-gtdeleteAll(rsquopost id=rsquo$this-gtid)

Tagmodel()-gtupdateFrequency($this-gttags rsquorsquo)

The above code is very straightforward it first deletes all those comments whose post id

is the same as the ID of the deleted post it then updates the tbl tag table for the tags

of the deleted post

34 Managing Posts 31

Tip We have to explicitly delete all comments for the deleted post here becauseSQLite does not really support foreign key constraints In a DBMS that supportsthis constraint (such as MySQL PostgreSQL) the foreign key constraint can be setup such that the DBMS automatically deletes the related comments if the post isdeleted In that case we no longer this explicit deletion call in our code

32 3 Post Management

Chapter 4

Comment Management

41 Customizing Comment Model

For the Comment model we mainly need to customize the rules() and attributeLabels()

methods The attributeLabels() method returns a mapping between attribute namesand attribute labels We do not need to touch relations() since the code generated bythe yiic tool is good enough

411 Customizing rules() Method

We first customize the validation rules generated by the yiic tool The following rules areused for comments

public function rules()

return array(

array(rsquocontent author emailrsquo rsquorequiredrsquo)

array(rsquoauthor email urlrsquo rsquolengthrsquo rsquomaxrsquo=gt128)

array(rsquoemailrsquorsquoemailrsquo)

array(rsquourlrsquorsquourlrsquo)

)

In the above we specify that the author email and content attributes are required thelength of author email and url cannot exceed 128 the email attribute must be a validemail address and the url attribute must be a valid URL

412 Customizing attributeLabels() Method

We then customize the attributeLabels() method to declare the label display for eachmodel attribute This method returns an array consisting of name-label pairs When wecall CHtmlactiveLabel() to display an attribute label

34 4 Comment Management

public function attributeLabels()

return array(

rsquoidrsquo =gt rsquoIdrsquo

rsquocontentrsquo =gt rsquoCommentrsquo

rsquostatusrsquo =gt rsquoStatusrsquo

rsquocreate timersquo =gt rsquoCreate Timersquo

rsquoauthorrsquo =gt rsquoNamersquo

rsquoemailrsquo =gt rsquoEmailrsquo

rsquourlrsquo =gt rsquoWebsitersquo

rsquopost idrsquo =gt rsquoPostrsquo

)

Tip If the label for an attribute is not declared in attributeLabels() an algo-rithm will be used to generate an appropriate label For example a label VerifyCode will be generated for attributes verify code or verifyCode

413 Customizing Saving Process

Because we want to record the creation time of a comment we override the beforeSave()

method of Comment like we do for the Post model

protected function beforeSave()

if(parentbeforeSave())

if($this-gtisNewRecord)

$this-gtcreate time=time()

return true

else

return false

42 Creating and Displaying Comments

In this section we implement the comment display and creation features

421 Displaying Comments

Instead of displaying and creating comments on individual pages we use the post detailpage (generated by the view action of PostController) Below the post content display

42 Creating and Displaying Comments 35

we display first a list of comments belonging to that post and then a comment creationform

In order to display comments on the post detail page we modify the view script wwwrootblogprotectedviewspostviewphp as follows

ltdiv id=commentsgt

ltphp if($model-gtcommentCountgt=1) gt

lth3gt

ltphp echo $model-gtcommentCount rsquocomment(s)rsquo gt

lth3gt

ltphp $this-gtrenderPartial(rsquo commentsrsquoarray(

rsquopostrsquo=gt$model

rsquocommentsrsquo=gt$model-gtcomments

)) gt

ltphp endif gt

ltdivgt

In the above we call renderPartial() to render a partial view named comments to displaythe list of comments belonging to the current post Note that in the view we use theexpression $model-gtcomments to retrieve the comments for the post This is valid becausewe have declared a comments relation in the Post class Evaluating this expression wouldtrigger an implicit JOIN database query to bring back the proper comments This featureis known as lazy relational query

The partial view comments is not very interesting It mainly goes through every commentand displays the detail of it Interested readers may refer to wwwrootyiidemosblog

protectedpost commentsphp

422 Creating Comments

To handle comment creation we first modify the actionView() method of PostController

as follows

public function actionView()

$post=$this-gtloadModel()

$comment=$this-gtnewComment($post)

$this-gtrender(rsquoviewrsquoarray(

rsquomodelrsquo=gt$post

rsquocommentrsquo=gt$comment

))

36 4 Comment Management

protected function newComment($post)

$comment=new Comment

if(isset($ POST[rsquoCommentrsquo]))

$comment-gtattributes=$ POST[rsquoCommentrsquo]

if($post-gtaddComment($comment))

if($comment-gtstatus==CommentSTATUS PENDING)

Yiiapp()-gtuser-gtsetFlash(rsquocommentSubmittedrsquorsquoThank yoursquo)

$this-gtrefresh()

return $comment

In the above we call the newComment() method before we render view In the newComment()

method we generate a Comment instance and check if the comment form is submitted If sowe try to add the comment for the post by calling $post-gtaddComment($comment) If it goesthrough we refresh the post detail page In case the comment needs to be approved we willshow a flash message to indicate this decision A flash message is usually a confirmationmessage displayed to end users If the user clicks on the refresh button of his browser themessage will disappear

We also need to modify wwwrootblogprotectedviewspostviewphp furthermore

ltdiv id=commentsgt

lth3gtLeave a Commentlth3gt

ltphp if(Yiiapp()-gtuser-gthasFlash(rsquocommentSubmittedrsquo)) gt

ltdiv class=successgt

ltphp echo Yiiapp()-gtuser-gtgetFlash(rsquocommentSubmittedrsquo) gt

ltdivgt

ltphp else gt

ltphp $this-gtrenderPartial(rsquocomment formrsquoarray(

rsquomodelrsquo=gt$comment

)) gt

ltphp endif gt

ltdivgtlt-- comments --gt

43 Managing Comments 37

In the above code we display the flash message if it is available If not we displaythe comment input form by rendering the partial view wwwrootblogprotectedviews

comment formphp

43 Managing Comments

Comment management includes updating deleting and approving comments These op-erations are implemented as actions in the CommentController class

431 Updating and Deleting Comments

The code generated by yiic for updating and deleting comments remains largely un-changed

432 Approving Comments

When comments are newly created they are in pending approval status and need to beapproved in order to be visible to guest users Approving a comment is mainly aboutchanging the status column of the comment

We create an actionApprove() method in CommentController as follows

public function actionApprove()

if(Yiiapp()-gtrequest-gtisPostRequest)

$comment=$this-gtloadModel()

$comment-gtapprove()

$this-gtredirect(array(rsquoindexrsquo))

else

throw new CHttpException(400rsquoInvalid requestrsquo)

In the above when the approve action is invoked via a POST request we call the approve()

method defined in the Comment model to change the status We then redirect the userbrowser to the page displaying the post that this comment belongs to

We also modify the actionIndex() method of Comment to show all comments We wouldlike to see comments pending approved to show up first

public function actionIndex()

38 4 Comment Management

$dataProvider=new CActiveDataProvider(rsquoCommentrsquo array(

rsquocriteriarsquo=gtarray(

rsquowithrsquo=gtrsquopostrsquo

rsquoorderrsquo=gtrsquotstatus tcreate time DESCrsquo

)

))

$this-gtrender(rsquoindexrsquoarray(

rsquodataProviderrsquo=gt$dataProvider

))

Notice that in the above code because both tbl post and tbl comment have columns statusand create time we need to disambiguate the corresponding column reference by prefixingthem with table alias names As described in the guide the alias for the primary table ina relational query is always t Therefore we are prefixing t to the status and create time

columns in the above code

Like the post index view the index view for CommentController uses [CListView] to displaythe comment list which in turn uses the partial view wwwrootblogprotectedviews

comment viewphp to display the detail of each individual comment We will not go intodetails here Interested readers may refer to the corresponding file in the blog demowwwrootyiidemosblogprotectedviewscomment viewphp

Chapter 5

Portlets

51 Customizing Page Layout

Features like rdquothe most recent commentsrdquo rdquotag cloudrdquo are better to be implemented inportlets A portlet is a pluggable user interface component that renders a fragment ofHTML code In this section we describe how to set up the portlet architecture for ourblog application

Before we go into details about developing portlets we first need to adjust our page layoutso that the portlets can be placed in a side bar

A page layout determines how to share common sections (eg header footer) amongdifferent pages and how to embed contents that are specific for different pages In Yiia page layout is represented as a view under the protectedviewslayouts folder Whenwe call the render() method in a controller the rendering result will be automaticallyembedded into this layout view before it is displayed to end users The default layoutview is named as main which means the wwwrootblogprotectedviewslayoutsmain

php file in our blog application

We could directly modify the main layout to add a side bar However doing so wouldmake some irrelevant pages such as about contact pages to have a side bar which is notalways desirable For this reason we need different layouts for different pages

We thus create two different layouts column1 and column2 The former displays the pagein a single column while the latter displays two columns in the page one of which is theside bar We will use column2 layout for the post and comment pages and column1 forthe rest of the pages Because both column1 and column2 still share the same header andfooter sections we let them to share the main layout

Based on the above description we create the column1 layout as follows

ltphp $this-gtbeginContent(rsquolayoutsmainrsquo) gt

ltdiv class=containergt

40 5 Portlets

ltdiv id=contentgt

ltphp echo $content gt

ltdivgtlt-- content --gt

ltdivgt

ltphp $this-gtendContent() gt

and the column2 layout as follows

ltphp $this-gtbeginContent(rsquolayoutsmainrsquo) gt

ltdiv class=containergt

ltdiv class=span-18gt

ltdiv id=contentgt

ltphp echo $content gt

ltdivgtlt-- content --gt

ltdivgt

ltdiv class=span-6 lastgt

ltdiv id=sidebargt

portlets to be placed here

ltdivgtlt-- sidebar --gt

ltdivgt

ltdivgt

ltphp $this-gtendContent() gt

As we can see both column1 and column2 are enclosed in a pair of beginContent andendContent calls They essentially use the specified view layoutsmain to decorate theenclosed content In all these layout views the variable $content refers to the dynamiccontent to be embedded into the layout

Besides customizing the layout view file we also need to adjust the CSS file wwwroot

blogcssmaincss so that the overall appearance would look like what we see in the blogdemo We will not go into details here

Info The skeleton application generated by the yiic tool uses the BluePrint CSSframework It provides a nice font reset and a robust grid system For more detailson how to use this framework please refer to its documentation

After creating the new layouts we need to set the layout property of controllers to makeuse of them So far we have three controllers SiteController PostController andCommentController We set SiteController to use column1 as the default layout whilethe rest two of the controllers use column2 for example

class PostController extends Controller

52 Creating User Menu Portlet 41

public $layout=rsquocolumn2rsquo

Tip If some view of the controller does not use the same layout as declaredby the layout property it may customize it in the view by a simple assignment$this-gtlayout=rsquonewlayoutrsquo

52 Creating User Menu Portlet

Based on the requirements analysis we need three different portlets the rdquouser menurdquoportlet the rdquotag cloudrdquo portlet and the rdquorecent commentsrdquo portlet We will develop theseportlets by extending the [CPortlet] widget provided by Yii

In this section we will develop our first concrete portlet - the user menu portlet whichdisplays a list of menu items that are only available to authenticated users The menucontains four items

bull Approve Comments a hyperlink that leads to a list of comments pending approval

bull Create New Post a hyperlink that leads to the post creation page

bull Manage Posts a hyperlink that leads to the post management page

bull Logout a link button that would log out the current user

521 Creating UserMenu Class

We create the UserMenu class to represent the logic part of the user menu portlet Theclass is saved in the file wwwrootblogprotectedcomponentsUserMenuphp which has thefollowing content

Yiiimport(rsquoziiwidgetsCPortletrsquo)

class UserMenu extends CPortlet

public function init()

$this-gttitle=CHtmlencode(Yiiapp()-gtuser-gtname)

parentinit()

42 5 Portlets

protected function renderContent()

$this-gtrender(rsquouserMenursquo)

The UserMenu class extends from the CPortlet class that we created previously It overridesboth the init() method and the renderContent() method of CPortlet The former setsthe portlet title to be the name of the current user the latter generates the portlet bodycontent by rendering a view named userMenu

Tip Notice that we have to explicitly include the CPortlet class by calling Yii

import() before we refer to it the first time This is because CPortlet is part of thezii project ndash the official extension library for Yii For performance considerationclasses in this project are not listed as core classes Therefore we have to import itbefore we use it the first time

522 Creating userMenu View

Next we create the userMenu view which is saved in the file wwwrootblogprotected

componentsviewsuserMenuphp

ltulgt

ltligtltphp echo CHtmllink(rsquoCreate New Postrsquoarray(rsquopostcreatersquo)) gtltligt

ltligtltphp echo CHtmllink(rsquoManage Postsrsquoarray(rsquopostadminrsquo)) gtltligt

ltligtltphp echo CHtmllink(rsquoApprove Commentsrsquoarray(rsquocommentindexrsquo))

rsquo (rsquo Commentmodel()-gtpendingCommentCount rsquo)rsquo gtltligt

ltligtltphp echo CHtmllink(rsquoLogoutrsquoarray(rsquositelogoutrsquo)) gtltligt

ltulgt

Info By default view files for a widget should be placed under the views sub-directory of the directory containing the widget class file The file name must bethe same as the view name

523 Using UserMenu Portlet

It is time for us to make use of our newly completed UserMenu portlet We modify thelayout view file wwwrootblogprotectedviewslayoutscolumn2php as follows

53 Creating Tag Cloud Portlet 43

ltdiv id=sidebargt

ltphp if(Yiiapp()-gtuser-gtisGuest) $this-gtwidget(rsquoUserMenursquo) gt

ltdivgt

In the above we call the widget() method to generate and execute an instance of theUserMenu class Because the portlet should only be displayed to authenticated users weonly call widget() when the isGuest property of the current user is false (meaning theuser is authenticated)

524 Testing UserMenu Portlet

Letrsquos test what we have so far

1 Open a browser window and enter the URL httpwwwexamplecomblogindex

php Verify that there is nothing displayed in the side bar section of the page

2 Click on the Login hyperlink and fill out the login form to login If successful verifythat the UserMenu portlet appears in the side bar and the portlet has the usernameas its title

3 Click on the rsquoLogoutrsquo hyperlink in the UserMenu portlet Verify that the logout actionis successful and the UserMenu portlet disappears

525 Summary

What we have created is a portlet that is highly reusable We can easily reuse it in adifferent project with little or no modification Moreover the design of this portlet followsclosely the philosophy that logic and presentation should be separated While we did notpoint this out in the previous sections such practice is used nearly everywhere in a typicalYii application

53 Creating Tag Cloud Portlet

Tag cloud displays a list of post tags with visual decorations hinting the popularity of eachindividual tag

531 Creating TagCloud Class

We create the TagCloud class in the file wwwrootblogprotectedcomponentsTagCloud

php The file has the following content

44 5 Portlets

Yiiimport(rsquoziiwidgetsCPortletrsquo)

class TagCloud extends CPortlet

public $title=rsquoTagsrsquo

public $maxTags=20

protected function renderContent()

$tags=Tagmodel()-gtfindTagWeights($this-gtmaxTags)

foreach($tags as $tag=gt$weight)

$link=CHtmllink(CHtmlencode($tag) array(rsquopostindexrsquorsquotagrsquo=gt$tag))

echo CHtmltag(rsquospanrsquo array(

rsquoclassrsquo=gtrsquotagrsquo

rsquostylersquo=gtfont-size$weightpt) $link)n

Unlike the UserMenu portlet the TagCloud portlet does not use a view Instead its presen-tation is done in the renderContent() method This is because the presentation does notcontain much HTML tags

We display each tag as a hyperlink to the post index page with the corresponding tagparameter The font size of each tag link is adjusted according to their relative weightamong other tags If a tag has higher frequency value than the other it will have a biggerfont size

532 Using TagCloud Portlet

Usage of the TagCloud portlet is very simple We modify the layout file wwwrootblog

protectedviewslayoutsmainphp as follows

ltdiv id=sidebargt

ltphp if(Yiiapp()-gtuser-gtisGuest) $this-gtwidget(rsquoUserMenursquo) gt

ltphp $this-gtwidget(rsquoTagCloudrsquo array(

rsquomaxTagsrsquo=gtYiiapp()-gtparams[rsquotagCloudCountrsquo]

)) gt

ltdivgt

54 Creating Recent Comments Portlet 45

54 Creating Recent Comments Portlet

In this section we create the last portlet that displays a list of comments recently pub-lished

541 Creating RecentComments Class

We create the RecentComments class in the file wwwrootblogprotectedcomponentsRecentCommentsphp The file has the following content

Yiiimport(rsquoziiwidgetsCPortletrsquo)

class RecentComments extends CPortlet

public $title=rsquoRecent Commentsrsquo

public $maxComments=10

public function getRecentComments()

return Commentmodel()-gtfindRecentComments($this-gtmaxComments)

protected function renderContent()

$this-gtrender(rsquorecentCommentsrsquo)

In the above we invoke the findRecentComments method which is defined in the Comment

class as follows

class Comment extends CActiveRecord

public function findRecentComments($limit=10)

return $this-gtwith(rsquopostrsquo)-gtfindAll(array(

rsquoconditionrsquo=gtrsquotstatus=rsquoselfSTATUS APPROVED

rsquoorderrsquo=gtrsquotcreate time DESCrsquo

rsquolimitrsquo=gt$limit

))

46 5 Portlets

542 Creating recentComments View

The recentComments view is saved in the file wwwrootblogprotectedcomponentsviews

recentCommentsphp It simply displays every comment returned by the RecentComments

getRecentComments() method

543 Using RecentComments Portlet

We modify the layout file wwwrootblogprotectedviewslayoutsmainphp to embedthis last portlet

ltdiv id=sidebargt

ltphp if(Yiiapp()-gtuser-gtisGuest) $this-gtwidget(rsquoUserMenursquo) gt

ltphp $this-gtwidget(rsquoTagCloudrsquo array(

rsquomaxTagsrsquo=gtYiiapp()-gtparams[rsquotagCloudCountrsquo]

)) gt

ltphp $this-gtwidget(rsquoRecentCommentsrsquo array(

rsquomaxCommentsrsquo=gtYiiapp()-gtparams[rsquorecentCommentCountrsquo]

)) gt

ltdivgt

Chapter 6

Final Work

61 Beautifying URLs

The URLs linking various pages of our blog application currently look ugly For examplethe URL for the page showing a post looks like the following

indexphpr=postshowampid=1

In this section we describe how to beautify these URLs and make them SEO-friendlyOur goal is to be able to use the following URLs in the application

1 indexphppostsyii leads to the page showing a list of posts with tag yii

2 indexphppost2A+Test+Post leads to the page showing the detail of the postwith ID 2 whose title is A Test Post

3 indexphppostupdate1 leads to the page that allows updating the post with ID1

Note that in the second URL format we include the post title in the URL This is mainlyto make the URL SEO friendly It is said that search engines may also respect the wordsfound in a URL when it is being indexed

To achieve our goal we modify the application configuration as follows

return array(

rsquocomponentsrsquo=gtarray(

rsquourlManagerrsquo=gtarray(

rsquourlFormatrsquo=gtrsquopathrsquo

rsquorulesrsquo=gtarray(

48 6 Final Work

rsquopostltidd+gtlttitlegtrsquo=gtrsquopostviewrsquorsquopostslttaggtrsquo=gtrsquopostindexrsquo

)

)

)

)

In the above we configure the urlManager component by setting its urlFormat propertyto be path and adding a set of rules

The rules are used by urlManager to parse and create the URLs in the desired formatFor example the first rule says that if a URL indexphppostsyii is requested theurlManager component should be responsible to dispatch the request to the route post

index and generate a tag GET parameter with the value yii On the other hand whencreating a URL with the route postindex and parameter tag the urlManager componentwill also use this rule to generate the desired URL indexphppostsyii For this reasonwe say that urlManager is a two-way URL manager

The urlManager component can further beautify our URLs such as hiding indexphp inthe URLs appending suffix like html to the URLs We can obtain these features easilyby configuring various properties of urlManager in the application configuration For moredetails please refer to the Guide

62 Logging Errors

A production Web application often needs sophisticated logging for various events In ourblog application we would like to log the errors occurring when it is being used Sucherrors could be programming mistakes or usersrsquo misuse of the system Logging these errorswill help us to improve the blog application

We enable the error logging by modifying the application configuration as follows

return array(

rsquopreloadrsquo=gtarray(rsquologrsquo)

rsquocomponentsrsquo=gtarray(

rsquologrsquo=gtarray(

rsquoclassrsquo=gtrsquoCLogRouterrsquo

rsquoroutesrsquo=gtarray(

array(

rsquoclassrsquo=gtrsquoCFileLogRoutersquo

rsquolevelsrsquo=gtrsquoerror warningrsquo

63 Final Tune-up and Deployment 49

)

)

)

)

)

With the above configuration if an error or warning occurs detailed information will belogged and saved in a file located under the directory wwwrootblogprotectedruntime

The log component offers more advanced features such as sending log messages to a listof email addresses displaying log messages in JavaScript console window etc For moredetails please refer to the Guide

63 Final Tune-up and Deployment

We are close to finish our blog application Before deployment we would like to do sometune-ups

631 Changing Home Page

We change to use the post list page as the home page We modify the application config-uration as follows

return array(

rsquodefaultControllerrsquo=gtrsquopostrsquo

)

Tip Because PostController already declares list to be its default action whenwe access the home page of the application we will see the result generated by thelist action of the post controller

632 Enabling Schema Caching

Because ActiveRecord relies on the metadata about tables to determine the column infor-mation it takes time to read the metadata and analyze it This may not be a problemduring development stage but for an application running in production mode it is a totalwaste of time if the database schema does not change Therefore we should enable theschema caching by modifying the application configuration as follows

50 6 Final Work

return array(

rsquocomponentsrsquo=gtarray(

rsquocachersquo=gtarray(

rsquoclassrsquo=gtrsquoCDbCachersquo

)

rsquodbrsquo=gtarray(

rsquoclassrsquo=gtrsquosystemdbCDbConnectionrsquo

rsquoconnectionStringrsquo=gtrsquosqlitewwwrootblogprotecteddatablogdbrsquo

rsquoschemaCachingDurationrsquo=gt3600

)

)

)

In the above we first add a cache component which uses a default SQLite database as thecaching storage If our server is equipped with other caching extensions such as APCwe could change to use them as well We also modify the db component by setting itsschemaCachingDuration property to be 3600 which means the parsed database schemadata can remain valid in cache for 3600 seconds

633 Disabling Debugging Mode

We modify the entry script file wwwrootblogindexphp by removing the line definingthe constant YII DEBUG This constant is useful during development stage because it allowsYii to display more debugging information when an error occurs However when theapplication is running in production mode displaying debugging information is not agood idea because it may contain sensitive information such as where the script file islocated and the content in the file etc

634 Deploying the Application

The final deployment process manly involves copying the directory wwwrootblog to thetarget directory The following checklist shows every needed step

1 Install Yii in the target place if it is not available

2 Copy the entire directory wwwrootblog to the target place

3 Edit the entry script file indexphp by pointing the $yii variable to the new Yiibootstrap file

4 Edit the file protectedyiicphp by setting the $yiic variable to be the new Yiiyiicphp file

64 Future Enhancements 51

5 Change the permission of the directories assets and protectedruntime so that theyare writable by the Web server process

64 Future Enhancements

641 Using a Theme

Without writing any code our blog application is already themeable To use a themewe mainly need to develop the theme by writing customized view files in the theme Forexample to use a theme named classic that uses a different page layout we would createa layout view file wwwrootblogthemesclassicviewslayoutsmainphp We also needto change the application configuration to indicate our choice of the classic theme

return array(

rsquothemersquo=gtrsquoclassicrsquo

)

642 Internationalization

We may also internationalize our blog application so that its pages can be displayed indifferent languages This mainly involves efforts in two aspects

First we may create view files in different languages For example for the list pageof PostController we can create a view file wwwrootblogprotectedviewspostzh cn

listphp When the application is configured to use simplified Chinese (the language codeis zh cn) Yii will automatically use this new view file instead of the original one

Second we may create message translations for those messages generated by code Themessage translations should be saved as files under the directory wwwrootblogprotected

messages We also need to modify the code where we use text strings by enclosing themin the method call Yiit()

For more details about internationalization please refer to the Guide

643 Improving Performance with Cache

While the Yii framework itself is very efficient it is not necessarily true that an applicationwritten in Yii efficient There are several places in our blog application that we can improvethe performance For example the tag clould portlet could be one of the performancebottlenecks because it involves complex database query and PHP logic

52 6 Final Work

We can make use of the sophisticated caching feature provided by Yii to improve theperformance One of the most useful components in Yii is COutputCache which caches afragment of page display so that the underlying code generating the fragment does not needto be executed for every request For example in the layout file wwwrootblogprotected

viewslayoutsmainphp we can enclose the tag cloud portlet with COutputCache

ltphp if($this-gtbeginCache(rsquotagCloudrsquo array(rsquodurationrsquo=gt3600))) gt

ltphp $this-gtwidget(rsquoTagCloudrsquo array(

rsquomaxTagsrsquo=gtYiiapp()-gtparams[rsquotagCloudCountrsquo]

)) gt

ltphp $this-gtendCache() gt

With the above code the tag cloud display will be served from cache instead of beinggenerated on-the-fly for every request The cached content will remain valid in cache for3600 seconds

644 Adding New Features

Our blog application only has very basic functionalities To become a complete blogsystem more features are needed for example calendar portlet email notifications postcategorization archived post portlet and so on We will leave the implementation of thesefeatures to interested readers

  • Contents
  • License
  • Getting Started
    • Building a Blog System using Yii
    • Testdriving with Yii
      • Installing Yii
      • Creating Skeleton Application
      • Application Workflow
        • Requirements Analysis
        • Overall Design
          • Initial Prototyping
            • Setting Up Database
              • Creating Database
              • Establishing Database Connection
                • Scaffolding
                • Authenticating User
                • Summary
                  • Post Management
                    • Customizing Post Model
                      • Customizing rules() Method
                      • Customizing relations() Method
                      • Representing Status in Text
                        • Creating and Updating Posts
                          • Customizing Access Control
                          • Customizing create and update Operations
                            • Displaying Posts
                              • Customizing view Operation
                              • Customizing index Operation
                                • Managing Posts
                                  • Listing Posts in Tabular View
                                  • Deleting Posts
                                      • Comment Management
                                        • Customizing Comment Model
                                          • Customizing rules() Method
                                          • Customizing attributeLabels() Method
                                          • Customizing Saving Process
                                            • Creating and Displaying Comments
                                              • Displaying Comments
                                              • Creating Comments
                                                • Managing Comments
                                                  • Updating and Deleting Comments
                                                  • Approving Comments
                                                      • Portlets
                                                        • Customizing Page Layout
                                                        • Creating User Menu Portlet
                                                          • Creating UserMenu Class
                                                          • Creating userMenu View
                                                          • Using UserMenu Portlet
                                                          • Testing UserMenu Portlet
                                                          • Summary
                                                            • Creating Tag Cloud Portlet
                                                              • Creating TagCloud Class
                                                              • Using TagCloud Portlet
                                                                • Creating Recent Comments Portlet
                                                                  • Creating RecentComments Class
                                                                  • Creating recentComments View
                                                                  • Using RecentComments Portlet
                                                                      • Final Work
                                                                        • Beautifying URLs
                                                                        • Logging Errors
                                                                        • Final Tune-up and Deployment
                                                                          • Changing Home Page
                                                                          • Enabling Schema Caching
                                                                          • Disabling Debugging Mode
                                                                          • Deploying the Application
                                                                            • Future Enhancements
                                                                              • Using a Theme
                                                                              • Internationalization
                                                                              • Improving Performance with Cache
                                                                              • Adding New Features
Page 10: Yii Blog 1.1.0 Demo Tutor

12 Testdriving with Yii 3

require once($yii)

YiicreateWebApplication($config)-gtrun()

This is the only script that Web users can directly access The script first includes theYii bootstrap file yiiphp It then creates an application instance with the specifiedconfiguration and executes the application

Base Application Directory

We also have an application base directory wwwrootblogprotected The majority of ourcode and data will be placed under this directory and it should be protected from beingaccessed by Web users For Apache httpd Web server we place under this directory ahtaccess file with the following content

deny from all

For other Web servers please refer to the corresponding manual on how to protect adirectory from being accessed by Web users

123 Application Workflow

To help understand how Yii works we describe the main workflow in our skeleton appli-cation when a user is accessing its contact page

1 The user requests the URL httpwwwexamplecomblogindexphpr=sitecontact

2 The entry script is executed by the Web server to process the request

3 An application instance is created and configured with initial property values speci-fied in the application configuration file wwwrootblogprotectedconfigmainphp

4 The application resolves the request into a controller and a controller action For thecontact page request it is resolved as the site controller and the contact action (theactionContact method in wwwrootblogprotectedcontrollersSiteControllerphp)

5 The application creates the site controller in terms of a SiteController instanceand then executes it

6 The SiteController instance executes the contact action by calling its actionContact()method

4 1 Getting Started

7 The actionContact method renders a view named contact to the Web user Inter-nally this is achieved by including the view file wwwrootblogprotectedviews

sitecontactphp and embedding the result into the layout file wwwrootblog

protectedviewslayoutsmainphp

13 Requirements Analysis

The blog system that we are going to develop is a single user system The owner of thesystem will be able to perform the following actions

bull Login and logout

bull Create update and delete posts

bull Publish unpublish and archive posts

bull Approve and delete comments

All other users are guest users who can perform the following actions

bull Read posts

bull Create comments

Additional Requirements for this system include

bull The homepage of the system should display a list of the most recent posts

bull If a page contains more than 10 posts they should be displayed in pages

bull The system should display a post together with its comments

bull The system should be able to list posts with a specified tag

bull The system should show a cloud of tags indicating their use frequencies

bull The system should show a list of most recent comments

bull The system should be themeable

bull The system should use SEO-friendly URLs

14 Overall Design 5

14 Overall Design

Based on the analysis of the requirements we decide to use the following database tablesto store the persistent data for our blog application

bull tbl user stores the user information including username and password

bull tbl post stores the blog post information It mainly consists of the following columns

ndash title required title of the post

ndash content required body content of the post which uses the Markdown format

ndash status required status of the post which can be one of following values

lowast 1 meaning the post is in draft and is not visible to public

lowast 2 meaning the post is published to public

lowast 3 meaning the post is outdated and is not visible in the post list (stillaccessible individually though)

ndash tags optional a list of comma-separated words categorizing the post

bull tbl comment stores the post comment information Each comment is associated witha post and mainly consists of the following columns

ndash name required the author name

ndash email required the author email

ndash website optional the author website URL

ndash content required the comment content in plain text format

ndash status required status of the comment which indicates whether the commentis approved (value 2) or not (value 1)

bull tbl tag stores post tag frequency information that is needed to implement the tagcloud feature The table mainly contains the following columns

ndash name required the unique tag name

ndash frequency required the number of times that the tag appears in posts

bull tbl lookup stores generic lookup information It is essentially a map between integervalues and text strings The former is the data representation in our code while thelatter is the corresponding presentation to end users For example we use integer1 to represent the draft post status and string Draft to display this status to endusers This table mainly contains the following columns

6 1 Getting Started

ndash name the textual representation of the data item that is to be displayed to endusers

ndash code the integer representation of the data item

ndash type the type of the data item

ndash position the relative display order of the data item among other items of thesame type

The following entity-relation (ER) diagram shows the table structure and relationshipsabout the above tables

Figure 11 Entity-Relation Diagram of the Blog Database

Complete SQL statements corresponding to the above ER diagram may be found inthe blog demo In our Yii installation they are in the file wwwrootyiidemosblog

protecteddataschemasqlitesql

Info We name all our table names and column names in lower case This is becausedifferent DBMS often have different case-sensitivity treatment and we want to avoidtroubles like this

We also prefix all our tables with tbl This serves for two purposes First the prefixintroduces a namespace to these tables in case when they need to coexist with othertables in the same database which often happens in a shared hosting environmentwhere a single database is being used by multiple applications Second using tableprefix reduces the possibility of having some table names that are reserved keywordsin DBMS

14 Overall Design 7

We divide the development of our blog application into the following milestones

bull Milestone 1 creating a prototype of the blog system It should consist of most ofthe required functionalities

bull Milestone 2 completing post management It includes creating listing showingupdating and deleting posts

bull Milestone 3 completing comment management It includes creating listing ap-proving updating and deleting post comments

bull Milestone 4 implementing portlets It includes user menu login tag cloud andrecent comments portlets

bull Milestone 5 final tune-up and deployment

8 1 Getting Started

Chapter 2

Initial Prototyping

21 Setting Up Database

Having created a skeleton application and finished the database design in this section wewill create the blog database and establish the connection to it in the skeleton application

211 Creating Database

We choose to create a SQLite database Because the database support in Yii is built on topof PDO we can easily switch to use a different type of DBMS (eg MySQL PostgreSQL)without the need to change our application code

We create the database file blogdb under the directory wwwrootblogprotecteddataNote that both the directory and the database file have to be writable by the Web serverprocess as required by SQLite We may simply copy the database file from the blogdemo in our Yii installation which is located at wwwrootyiidemosblogprotecteddatablogdb We may also generate the database by executing the SQL statements in the filewwwrootyiidemosblogprotecteddataschemasqlitesql

Tip To execute SQL statements we may use the sqlite3 command line tool thatcan be found in the SQLite official website

212 Establishing Database Connection

To use the blog database in the skeleton application we created we need to modify itsapplication configuration which is stored in the PHP script wwwrootblogprotected

configmainphp The script returns an associative array consisting of name-value pairseach of which is used to initialize a writable property of the application instance

We configure the db component as follows

10 2 Initial Prototyping

return array(

rsquocomponentsrsquo=gtarray(

rsquodbrsquo=gtarray(

rsquoconnectionStringrsquo=gtrsquosqlitewwwrootblogprotecteddatablogdbrsquo

rsquotablePrefixrsquo=gtrsquotbl rsquo

)

)

)

The above configuration says that we have a db application component whose connectionStringproperty should be initialized as sqlitewwwrootblogprotecteddatablogdb and whosetablePrefix property should be tbl

With this configuration we can access the DB connection object using Yiiapp()-gtdb

at any place in our code Note that Yiiapp() returns the application instance that wecreate in the entry script If you are interested in possible methods and properties that theDB connection has you may refer to its class reference However in most cases we are notgoing to use this DB connection directly Instead we will use the so-called ActiveRecordto access the database

We would like to explain a bit more about the tablePrefix property that we set in theconfiguration This tells the db connection that it should respect the fact we are usingtbl as the prefix to our database table names In particular if in a SQL statement thereis a token enclosed within double curly brackets (eg post then the db connectionshould translate it into a name with the table prefix (eg tbl post) before sending it toDBMS for execution This feature is especially useful if in future we need to modify thetable name prefix without touching our source code For example if we are developing ageneric content management system (CMS) we may exploit this feature so that when itis being installed in a new environment we can allow users to choose a table prefix theylike

22 Scaffolding 11

Tip If you want to use MySQL instead of SQLite to store data you may cre-ate a MySQL database named blog using the SQL statements in wwwrootyii

demosblogprotecteddataschemamysqlsql Then modify the applicationconfiguration as follows

return array(

rsquocomponentsrsquo=gtarray(

rsquodbrsquo=gtarray(

rsquoconnectionStringrsquo =gt rsquomysqlhost=localhostdbname=blogrsquo

rsquoemulatePreparersquo =gt true

rsquousernamersquo =gt rsquorootrsquo

rsquopasswordrsquo =gt rsquorsquo

rsquocharsetrsquo =gt rsquoutf8rsquo

rsquotablePrefixrsquo =gt rsquotbl rsquo

)

)

)

22 Scaffolding

Create read update and delete (CRUD) are the four basic operations of data objects in anapplication Because the task of implementing the CRUD operations is so common whendeveloping Web applications Yii provides a tool to automate this process (also known asscaffolding) In this section we will describe how to use the tool to implement CRUD forposts and comments

Open a command window and run the following commands

wwwrootyiiframeworkyiic shell wwwrootblogindexphp

Yii Interactive Tool v11

Please type rsquohelprsquo for help Type rsquoexitrsquo to quit

gtgt model

gtgt crud Post

gtgt crud Comment

gtgt exit

12 2 Initial Prototyping

Info Some PHP installations may use a different phpini file for command line(CLI) PHP parser As a result when running the above yiic commands youmay encounter errors like rdquoYiiBaseinclude(PDOphp) failed to open streamrdquoor rdquocould not find driverrdquo Please double check your CLI PHP configuration byexecuting the following command

php -r phpinfo()

The result of the above command will show which phpini file is being used andwhich extensions are loaded If a wrong phpini file is used you may use thefollowing command to explicitly specify the correct phpini to use

php -c phpini wwwrootyiiframeworkyiicphp shell wwwrootblogindexphp

The commands above accomplish two tasks First the model command generates a modelclass file for each table in the blog database Second the crud commands generate thecode needed by the CRUD operations for the Post and Comment models

Tip The command model generates a model class for every table in the databaseSometimes we probably donrsquot want to do so (eg the database contains some irrel-evant tables) In this case we can create model classes one by one For exampleto create the User model we can use the command model User The model com-mand also has some more advanced usages For more details execute help model

command

We can test the generated code by accessing the following URLs

httpwwwexamplecomblogindexphpr=post

httpwwwexamplecomblogindexphpr=comment

Notice that the post and comment features implemented by the generated code are com-pletely independent of each other Also when creating a new post or comment we arerequired to enter information such as author id and create time which in real applica-tion should be set by the program Donrsquot worry We will fix these problems in the nextmilestones For now we should be fairly satisfied as this prototype already contains mostfeatures that we need to implement for the blog application

To prepare for the next milestones letrsquos take a closer look at the files generated by theabove commands All the files are generated under wwwrootblogprotected For conve-nience we group them into model files controller files and view files

22 Scaffolding 13

bull model files

ndash modelsUserphp contains the User class that extends from CActiveRecord andcan be used to access the tbl user database table

ndash modelsPostphp contains the Post class that extends from CActiveRecord andcan be used to access the tbl post database table

ndash modelsTagphp contains the Tag class that extends from CActiveRecord andcan be used to access the tbl tag database table

ndash modelsCommentphp contains the Comment class that extends from CActiveRe-cord and can be used to access the tbl comment database table

ndash modelsLookupphp contains the Lookup class that extends from CActiveRecordand can be used to access the tbl lookup database table

bull controller file

ndash controllersPostControllerphp contains the PostController class which is thecontroller in charge of all CRUD operations about posts

ndash controllersCommentControllerphp contains the CommentController class whichis the controller in charge of all CRUD operations about comments

bull view files

ndash viewspostcreatephp is the view file that shows an HTML form to create anew post

ndash viewspostupdatephp is the view file that shows an HTML form to update anexisting post

ndash viewspostviewphp is the view file that displays the detailed information of apost

ndash viewspostindexphp is the view file that displays a list of posts

ndash viewspostadminphp is the view file that displays posts in a table with ad-ministrative commands

ndash viewspost formphp is the partial view file embedded in viewspostcreate

php and viewspostupdatephp It displays the HTML form for collecting postinformation

ndash viewspost viewphp is the partial view file used by viewspostindexphp Itdisplays the brief view of a single post

ndash a similar set of view files are also generated for comment

14 2 Initial Prototyping

In order to understand better how the above files are used we show in the following theworkflow that occurs in the blog application when displaying a list of posts

1 The user requests the URL httpwwwexamplecomblogindexphpr=posts

2 The entry script is executed by the Web server which creates and initializes anapplication instance to handle the request

3 The application creates an instance of PostController and executes it

4 The PostController instance executes the index action by calling its actionIndex()

method Note that index is the default action if the user does not specify an actionto execute in the URL

5 The actionIndex() method queries database to bring back the list of recent posts

6 The actionIndex() method renders the index view with the post data

23 Authenticating User

Our blog application needs to differentiate between the system owner and guest usersTherefore we need to implement the user authentication feature

As you may have found that the skeleton application already provides user authenticationby checking if the username and password are both demo or admin In this section wewill modify the corresponding code so that the authentication is done against the User

database table

User authentication is performed in a class implementing the IUserIdentity interface Theskeleton application uses the UserIdentity class for this purpose The class is stored inthe file wwwrootblogprotectedcomponentsUserIdentityphp

Tip By convention the name of a class file must be the same as the correspondingclass name suffixed with the extension php Following this convention one canrefer to a class using a path alias For example we can refer to the UserIdentity

class with the alias applicationcomponentsUserIdentity Many APIs in Yiican recognize path aliases (eg YiicreateComponent()) and using path aliasesavoids the necessity of embedding absolute file paths in the code The existence ofthe latter often causes trouble when we deploy an application

We modify the UserIdentity class as follows

23 Authenticating User 15

ltphp

class UserIdentity extends CUserIdentity

private $ id

public function authenticate()

$username=strtolower($this-gtusername)

$user=Usermodel()-gtfind(rsquoLOWER(username)=rsquoarray($username))

if($user===null)

$this-gterrorCode=selfERROR USERNAME INVALID

else if($user-gtvalidatePassword($this-gtpassword))

$this-gterrorCode=selfERROR PASSWORD INVALID

else

$this-gt id=$user-gtid

$this-gtusername=$user-gtusername

$this-gterrorCode=selfERROR NONE

return $this-gterrorCode==selfERROR NONE

public function getId()

return $this-gt id

In the authenticate() method we use the User class to look for a row in the User tablewhose username column is the same as the given username in a case-insensitive mannerRemember that the User class was created using the yiic tool in the prior section Becausethe User class extends from CActiveRecord we can exploit the ActiveRecord feature toaccess the User table in an OOP fashion

In order to check if the user has entered a valid password we invoke the validatePassword

method of the User class We need to modify the file wwwrootblogprotectedmodels

Userphp as follows Note that instead of storing the plain password in the database westore the hash result of the password and a randomly generated salt key When validatingthe user-entered password we should compare the hash results instead

class User extends CActiveRecord

public function validatePassword($password)

return $this-gthashPassword($password$this-gtsalt)===$this-gtpassword

16 2 Initial Prototyping

public function hashPassword($password$salt)

return md5($salt$password)

In the UserIdentity class we also override the getId() method which returns the id valueof the user found in the User table The parent implementation would return the usernameinstead Both the username and id properties will be stored in the user session and maybe accessed via Yiiapp()-gtuser from anywhere in our code

Tip In the UserIdentity class we reference the class CUserIdentity without ex-plicitly including the corresponding class file This is because CUserIdentity is acore class provided by the Yii framework Yii will automatically include the classfile for any core class when it is referenced for the first time

We also do the same with the User class This is because the User class file is placedunder the directory wwwrootblogprotectedmodels which has been added tothe PHP include path according to the following lines found in the applicationconfiguration

return array(

rsquoimportrsquo=gtarray(

rsquoapplicationmodelsrsquo

rsquoapplicationcomponentsrsquo

)

)

The above configuration says that any class whose class file is located under either wwwrootblogprotectedmodels or wwwrootblogprotectedcomponents willbe automatically included when the class is referenced for the first time

The UserIdentity class is mainly used by the LoginForm class to authenticate a user basedon the username and password input collected from the login page The following codefragment shows how UserIdentity is used

$identity=new UserIdentity($username$password)

$identity-gtauthenticate()

switch($identity-gterrorCode)

24 Summary 17

case UserIdentityERROR NONE

Yiiapp()-gtuser-gtlogin($identity)

break

Info People often get confused about identity and the user application componentThe former represents a way of performing authentication while the latter is usedto represent the information related with the current user An application can onlyhave one user component but it can have one or several identity classes dependingon what kind of authentication it supports Once authenticated an identity instancemay pass its state information to the user component so that they are globallyaccessible via user

To test the modified UserIdentity class we can browse the URL httpwwwexample

comblogindexphp and try logging in with the username and password that we store inthe User table If we use the database provided by the blog demo we should be able tologin with username demo and password demo Note that this blog system does not providethe user management feature As a result a user cannot change his account or create anew one through the Web interface The user management feature may be considered asa future enhancement to the blog application

24 Summary

We have completed the milestone 1 Letrsquos summarize what we have done so far

1 We identified the requirements to be fulfilled

2 We installed the Yii framework

3 We created a skeleton application

4 We designed and created the blog database

5 We modified the application configuration by adding the database connection

6 We generated the code that implements the basic CRUD operations for both postsand comments

7 We modified the authentication method to check against the tbl user table

18 2 Initial Prototyping

For a new project most of the time will be spent in step 1 and 4 for this first milestone

Although the code generated by the yiic tool implements fully functional CRUD oper-ations for a database table it often needs to be modified in practical applications Forthis reason in the next two milestone our job is to customize the generated CRUD codeabout posts and comments so that it reaches our initial requirements

In general we first modify the model class file by adding appropriate validation rules anddeclaring relational objects We then modify the controller action and view code for eachindividual CRUD operation

Chapter 3

Post Management

31 Customizing Post Model

The Post model class generated by the yiic tool mainly needs to be modified in two places

bull the rules() method specifies the validation rules for the model attributes

bull the relations() method specifies the related objects

Info A model consists of a list of attributes each associated with a column in thecorresponding database table Attributes can be declared explicitly as class membervariables or implicitly without any declaration

311 Customizing rules() Method

We first specify the validation rules which ensure the attribute values entered by usersare correct before they are saved to the database For example the status attribute ofPost should be an integer 1 2 or 3 The yiic tool also generates validation rules for eachmodel However these rules are based on the table column information and may not beappropriate

Based on the requirement analysis we modify the rules() method as follows

public function rules()

return array(

array(rsquotitle content statusrsquo rsquorequiredrsquo)

array(rsquotitlersquo rsquolengthrsquo rsquomaxrsquo=gt128)

array(rsquostatusrsquo rsquoinrsquo rsquorangersquo=gtarray(123))

array(rsquotagsrsquo rsquomatchrsquo rsquopatternrsquo=gtrsquo^[ws]+$rsquorsquomessagersquo=gtrsquoTags can only contain word charactersrsquo)

20 3 Post Management

array(rsquotagsrsquo rsquonormalizeTagsrsquo)

)

In the above we specify that the title content and status attributes are required thelength of title should not exceed 128 the status attribute value should be 1 (draft) 2(published) or 3 (archived) and the tags attribute should only contain word charactersand commas In addition we use normalizeTags to normalize the user-entered tags so thatthe tags are unique and properly separated with commas

The validators such as required length in and match are all built-in validators providedby Yii The normalizeTags validator is a method-based validator that we need to definein the Post class For more information about how to specify validation rules please referto the Guide

public function normalizeTags($attribute$params)

$this-gttags=Tagarray2string(array unique(Tagstring2array($this-gttags)))

The rules declared in this method are executed one by one when we call the validate() orsave() method of the model instance

Note It is very important to remember that attributes appearing in rules() mustbe those to be entered by end users Other attributes such as id and create time

in the Post model which are set by our code or database should not be in rules()For more details please refer to Securing Attribute Assignments

After making these changes we can visit the post creation page again to verify that thenew validation rules are taking effect

312 Customizing relations() Method

Lastly we customize the relations() method to specify the related objects of a post Bydeclaring these related objects in relations() we can exploit the powerful RelationalActiveRecord (RAR) feature to access the related object information of a post such asits author and comments without the need to write complex SQL JOIN statements

We customize the relations() method as follows

31 Customizing Post Model 21

public function relations()

return array(

rsquoauthorrsquo =gt array(selfBELONGS TO rsquoUserrsquo rsquoauthor idrsquo)

rsquocommentsrsquo =gt array(selfHAS MANY rsquoCommentrsquo rsquopost idrsquo

rsquoconditionrsquo=gtrsquocommentsstatus=rsquoCommentSTATUS APPROVED

rsquoorderrsquo=gtrsquocommentscreate time DESCrsquo)

rsquocommentCountrsquo =gt array(selfSTAT rsquoCommentrsquo rsquopost idrsquo

rsquoconditionrsquo=gtrsquostatus=rsquoCommentSTATUS APPROVED)

)

The above relations state that

bull A post belongs to an author whose class is User and the relationship is establishedbased on the author id attribute value of the post

bull A post has many comments whose class is Comment and the relationship is establishedbased on the post id attribute value of the comments These comments should besorted according to their creation time and the comments must be approved

bull The commentCount relation is a bit special as it returns back an aggregation resultwhich is about how many comments the post has

With the above relation declaration we can easily access the author and comments of apost like the following

$author=$post-gtauthor

echo $author-gtusername

$comments=$post-gtcomments

foreach($comments as $comment)

echo $comment-gtcontent

For more details about how to declare and use relations please refer to the Guide

313 Representing Status in Text

Because the status of a post is stored as an integer in the database we need to provide atextual representation so that it is more intuitive when being displayed to end users In alarge system the similar requirement is very common

22 3 Post Management

As a generic solution we use the tbl lookup table to store the mapping between integervalues and textual representations that are needed by other data objects We modify theLookup model class as follows to more easily access the textual data in the table

class Lookup extends CActiveRecord

private static $ items=array()

public static function items($type)

if(isset(self$ items[$type]))

selfloadItems($type)

return self$ items[$type]

public static function item($type$code)

if(isset(self$ items[$type]))

selfloadItems($type)

return isset(self$ items[$type][$code]) self$ items[$type][$code] false

private static function loadItems($type)

self$ items[$type]=array()

$models=selfmodel()-gtfindAll(array(

rsquoconditionrsquo=gtrsquotype=typersquo

rsquoparamsrsquo=gtarray(rsquotypersquo=gt$type)

rsquoorderrsquo=gtrsquopositionrsquo

))

foreach($models as $model)

self$ items[$type][$model-gtcode]=$model-gtname

Our new code mainly provides two static methods Lookupitems() and Lookupitem()The former returns a list of strings belonging to the specified data type while the latterreturns a particular string for the given data type and data value

Our blog database is pre-populated with two lookup types PostStatus and CommentStatusThe former refers to the possible post statuses while the latter the comment statuses

In order to make our code easier to read we also declare a set of constants to representthe status integer values We should use these constants through our code when referringto the corresponding status values

32 Creating and Updating Posts 23

class Post extends CActiveRecord

const STATUS DRAFT=0

const STATUS PUBLISHED=1

const STATUS ARCHIVED=2

Therefore we can call Lookupitems(rsquoPostStatusrsquo) to get the list of possible post statuses(text strings indexed by the corresponding integer values) and call Lookupitem(rsquoPostStatusrsquoPostSTATUS PUBLISHED) to get the string representation of the published status

32 Creating and Updating Posts

With the Post model ready we need to fine-tune the actions and views for the controllerPostController In this section we first customize the access control of CRUD operationswe then modify the code implementing the create and update operations

321 Customizing Access Control

The first thing we want to do is to customize the access control because the code generatedby yiic does not fit our needs

We modify the accessRules() method in the file wwwrootblogprotectedcontrollers

PostControllerphp as follows

public function accessRules()

return array(

array(rsquoallowrsquo allow all users to perform rsquolistrsquo and rsquoshowrsquo actions

rsquoactionsrsquo=gtarray(rsquoindexrsquo rsquoviewrsquo)

rsquousersrsquo=gtarray(rsquorsquo)

)

array(rsquoallowrsquo allow authenticated users to perform any action

rsquousersrsquo=gtarray(rsquorsquo)

)

array(rsquodenyrsquo deny all users

rsquousersrsquo=gtarray(rsquorsquo)

)

)

The above rules state that all users can access the index and view actions and authenti-cated users can access any actions including the admin action The user should be denied

24 3 Post Management

access in any other scenario Note that these rules are evaluated in the order they arelisted here The first rule matching the current context makes the access decision Forexample if the current user is the system owner who tries to visit the post creation pagethe second rule will match and it will give the access to the user

322 Customizing create and update Operations

The create and update operations are very similar They both need to display an HTMLform to collect user inputs validate them and save them into database The main differ-ence is that the update operation will pre-populate the form with the existing post datafound in the database For this reason the yiic tool generates a partial view wwwroot

blogprotectedviewspost formphp that is embedded in both the create and update

views to render the needed HTML form

We first change the formphp file so that the HTML form only collects the inputs wewant title content tags and status We use plain text fields to collect inputs for thefirst three attributes and a dropdown list to collect input for status The dropdown listoptions are the text displays of the possible post statuses

ltphp echo CHtmlactiveDropDownList($postrsquostatusrsquoLookupitems(rsquoPostStatusrsquo)) gt

In the above we call Lookupitems(rsquoPostStatusrsquo) to bring back the list of post statuses

We then modify the Post class so that it can automatically set some attributes (eg create

time author id) before a post is saved to the database We override the beforeSave()

method as follows

protected function beforeSave()

if(parentbeforeSave())

if($this-gtisNewRecord)

$this-gtcreate time=$this-gtupdate time=time()

$this-gtauthor id=Yiiapp()-gtuser-gtid

else

$this-gtupdate time=time()

return true

else

return false

33 Displaying Posts 25

When we save a post we want to update the tbl tag table to reflect the change of tagfrequencies We can do this work in the afterSave() method which is automaticallyinvoked by Yii after a post is successfully saved into the database

protected function afterSave()

parentafterSave()

Tagmodel()-gtupdateFrequency($this-gt oldTags $this-gttags)

private $ oldTags

protected function afterFind()

parentafterFind()

$this-gt oldTags=$this-gttags

In the implementation because we want to detect if the user changes the tags in case he isupdating an existing post we need to know what the old tags are For this reason we alsowrite the afterFind() method to keep the old tags in the variable oldTags The methodafterFind() is invoked automatically by Yii when an AR record is populated with thedata from database

We are not going to give details of the TagupdateFrequency() method here Interestedreaders may refer to the file wwwrootyiidemosblogprotectedmodelsTagphp

33 Displaying Posts

In our blog application a post may be displayed among a list of posts or by itself Theformer is implemented as the index operation while the latter the view operation In thissection we customize both operations to fulfill our initial requirements

331 Customizing view Operation

The view operation is implemented by the actionView() method in PostController Itsdisplay is generated by the view view with the view file wwwrootblogprotectedviews

postviewphp

Below is the relevant code implementing the view operation in PostController

public function actionView()

26 3 Post Management

$this-gtrender(rsquoviewrsquoarray(

$post=$this-gtloadModel()

$this-gtrender(rsquoviewrsquoarray(

rsquomodelrsquo=gt$post

))

))

private $ model

public function loadModel()

if($this-gt model===null)

if(isset($ GET[rsquoidrsquo]))

if(Yiiapp()-gtuser-gtisGuest)

$condition=rsquostatus=rsquoPostSTATUS PUBLISHED

rsquo OR status=rsquoPOSTSTATUS ARCHIVED

else

$condition=rsquorsquo

$this-gt model=Postmodel()-gtfindbyPk($ GET[rsquoidrsquo] $condition)

if($this-gt model===null)

throw new CHttpException(404rsquoThe requested page does not existrsquo)

return $this-gt model

Our change mainly lies in the loadModel() method In this method we query the Post

table according to the id GET parameter If the post is not found or if it is not publishedor archived (when the user is a guest) we will throw a 404 HTTP error Otherwise thepost object is returned to actionView() which in turn passes the post object to the viewscript for further display

Tip Yii captures HTTP exceptions (instances of CHttpException) and displaysthem in either predefined templates or customized error views The skeleton appli-cation generated by yiic already contains a customized error view in wwwroot

blogprotectedviewssiteerrorphp We can modify this file if we want tofurther customize the error display

The change in the view script is mainly about ajdusting the formatting and styles of thepost display We will not go into details here Interested readers may refer to wwwroot

blogprotectedviewspostviewphp

33 Displaying Posts 27

332 Customizing index Operation

Like the view operation we customize the index operation in two places the actionIndex()

method in PostController and the view file wwwrootblogprotectedviewspostindex

php We mainly need to add the support for displaying a list of posts that are associatedwith a specified tag

Below is the modified actionIndex() method in PostController

public function actionIndex()

$criteria=new CDbCriteria(array(

rsquoconditionrsquo=gtrsquostatus=rsquoPostSTATUS PUBLISHED

rsquoorderrsquo=gtrsquoupdate time DESCrsquo

rsquowithrsquo=gtrsquocommentCountrsquo

))

if(isset($ GET[rsquotagrsquo]))

$criteria-gtaddSearchCondition(rsquotagsrsquo$ GET[rsquotagrsquo])

$dataProvider=new CActiveDataProvider(rsquoPostrsquo array(

rsquopaginationrsquo=gtarray(

rsquopageSizersquo=gtYiiapp()-gtparams[rsquopostsPerPagersquo]

)

rsquocriteriarsquo=gt$criteria

))

$this-gtrender(rsquoindexrsquoarray(

rsquodataProviderrsquo=gt$dataProvider

))

In the above we first create a query criteria for retrieving post list The criteria statesthat only published posts should be returned and they should be sorted according to theirupdate time in descending order Because when displaying a post in the list we want toshow how many comments the post has received in the criteria we also specify to bringback commentCount which if you remember is a relation declared in Postrelations()

In case when a user wants to see posts with a specific tag we would add a search conditionto the criteria to look for the specified tag

Using the query criteria we create a data provider which mainly serves for three purposesFirst it does pagination of the data when too many results may be returned Herewe customize the pagination by setting the page size to be an application parameterpostsPerPage Second it does sorting according to the user request And finally it feedsthe paginated and sorted data to widgets or view code for presentation

28 3 Post Management

After we finish with actionIndex() we modify the index view as follows Our change ismainly about adding the h1 header when the user specifies to display posts with a tag

ltphp if(empty($ GET[rsquotagrsquo])) gt

lth1gtPosts Tagged with ltigtltphp echo CHtmlencode($ GET[rsquotagrsquo]) gtltigtlth1gt

ltphp endif gt

ltphp $this-gtwidget(rsquoziiwidgetsCListViewrsquo array(

rsquodataProviderrsquo=gt$dataProvider

rsquoitemViewrsquo=gtrsquo viewrsquo

rsquotemplatersquo=gtitemsnpager)) gt

Note that in the above we use [CListView] to display the post list This widget requires apartial view to display the detail of each individual post Here we specify the partial viewto be view which means the file wwwrootblogprotectedviewspost viewphp In thisview script we can acccess the post instance being displayed via a local variable named$data

34 Managing Posts

Managing posts mainly refers to listing posts in an administrative view that allows us tosee posts with all statuses updating them and deleting them They are accomplished bythe admin operation and the delete operation respectively The code generated by yiic

does not need much modification Below we mainly explain how these two operations areimplemented

341 Listing Posts in Tabular View

The admin operation shows posts with all statuses in a tabular view The view supportssorting and pagination The following is the actionAdmin() method in PostController

public function actionAdmin()

$dataProvider=new CActiveDataProvider(rsquoPostrsquo array(

rsquosortrsquo=gtarray(

rsquodefaultOrderrsquo=gtrsquostatus update time DESCrsquo

)

))

$this-gtrender(rsquoadminrsquoarray(

rsquodataProviderrsquo=gt$dataProvider

))

34 Managing Posts 29

The above code is very similar to that in actionIndex() The main difference is in thequery criteria Here we do not specify any particular query criteria since we want to seeall posts We do customize the sort property of the data provider so that by default theposts are shown in the order of their statuses and update time

Below is the code for the admin view

lth1gtManage Postslth1gt

ltphp $this-gtwidget(rsquoziiwidgetsgridCGridViewrsquo array(

rsquodataProviderrsquo=gt$dataProvider

rsquocolumnsrsquo=gtarray(

array(

rsquonamersquo=gtrsquotitlersquo

rsquotypersquo=gtrsquorawrsquo

rsquovaluersquo=gtrsquoCHtmllink(CHtmlencode($data-gttitle) $data-gturl)rsquo

)

array(

rsquonamersquo=gtrsquostatusrsquo

rsquovaluersquo=gtrsquoLookupitem(PostStatus$data-gtstatus)rsquo

)

rsquocreate timedatetimersquo

rsquoupdate timedatetimersquo

array(

rsquoclassrsquo=gtrsquoCButtonColumnrsquo

)

)

)) gt

We use [CGridView] to display the posts It allows us to sort by a column and paginatethrough the posts if there are too many to be displayed in a single page Our change ismainly about how to display each column For example for the title column we specifythat it should be displayed as a hyperlink that points to the detailed view of the post

Tip When displaying text we call CHtmlencode() to encode HTML entities init This prevents from cross-site scripting attack

342 Deleting Posts

In the admin data grid there is a delete button in each row Clicking on the button shoulddelete the corresponding post Internally this triggers the delete action implemented asfollows

30 3 Post Management

public function actionDelete()

if(Yiiapp()-gtrequest-gtisPostRequest)

we only allow deletion via POST request

$this-gtloadModel()-gtdelete()

if(isset($ POST[rsquoajaxrsquo]))

$this-gtredirect(array(rsquoindexrsquo))

else

throw new CHttpException(400rsquoInvalid request Please do not repeat this request againrsquo)

The above code is the one generated by the yiic tool without any change We wouldlike to explain a little bit more about the checking on $ POST[rsquoajaxrsquo] The [CGridView]widget has a very nice feature that its sorting pagination and deletion operations are alldone in AJAX mode by default That means the whole page does not get reloaded if anyof the above operations is performed However it is also possible that the widget runs innon-AJAX mode (by setting its ajaxUpdate property to be false or disabling JavaScript onthe client side) It is necessary for the delete action to differentiate these two scenarios ifthe delete request is made via AJAX we should not redirect the user browser otherwisewe should

Deleting a post should also cause the deletion of all comments for that post In additionwe should also update the tbl tag table regarding the tags for the deleted post Both ofthese tasks can be achieved by writing an afterDelete method in the Post model class asfollows

protected function afterDelete()

parentafterDelete()

Commentmodel()-gtdeleteAll(rsquopost id=rsquo$this-gtid)

Tagmodel()-gtupdateFrequency($this-gttags rsquorsquo)

The above code is very straightforward it first deletes all those comments whose post id

is the same as the ID of the deleted post it then updates the tbl tag table for the tags

of the deleted post

34 Managing Posts 31

Tip We have to explicitly delete all comments for the deleted post here becauseSQLite does not really support foreign key constraints In a DBMS that supportsthis constraint (such as MySQL PostgreSQL) the foreign key constraint can be setup such that the DBMS automatically deletes the related comments if the post isdeleted In that case we no longer this explicit deletion call in our code

32 3 Post Management

Chapter 4

Comment Management

41 Customizing Comment Model

For the Comment model we mainly need to customize the rules() and attributeLabels()

methods The attributeLabels() method returns a mapping between attribute namesand attribute labels We do not need to touch relations() since the code generated bythe yiic tool is good enough

411 Customizing rules() Method

We first customize the validation rules generated by the yiic tool The following rules areused for comments

public function rules()

return array(

array(rsquocontent author emailrsquo rsquorequiredrsquo)

array(rsquoauthor email urlrsquo rsquolengthrsquo rsquomaxrsquo=gt128)

array(rsquoemailrsquorsquoemailrsquo)

array(rsquourlrsquorsquourlrsquo)

)

In the above we specify that the author email and content attributes are required thelength of author email and url cannot exceed 128 the email attribute must be a validemail address and the url attribute must be a valid URL

412 Customizing attributeLabels() Method

We then customize the attributeLabels() method to declare the label display for eachmodel attribute This method returns an array consisting of name-label pairs When wecall CHtmlactiveLabel() to display an attribute label

34 4 Comment Management

public function attributeLabels()

return array(

rsquoidrsquo =gt rsquoIdrsquo

rsquocontentrsquo =gt rsquoCommentrsquo

rsquostatusrsquo =gt rsquoStatusrsquo

rsquocreate timersquo =gt rsquoCreate Timersquo

rsquoauthorrsquo =gt rsquoNamersquo

rsquoemailrsquo =gt rsquoEmailrsquo

rsquourlrsquo =gt rsquoWebsitersquo

rsquopost idrsquo =gt rsquoPostrsquo

)

Tip If the label for an attribute is not declared in attributeLabels() an algo-rithm will be used to generate an appropriate label For example a label VerifyCode will be generated for attributes verify code or verifyCode

413 Customizing Saving Process

Because we want to record the creation time of a comment we override the beforeSave()

method of Comment like we do for the Post model

protected function beforeSave()

if(parentbeforeSave())

if($this-gtisNewRecord)

$this-gtcreate time=time()

return true

else

return false

42 Creating and Displaying Comments

In this section we implement the comment display and creation features

421 Displaying Comments

Instead of displaying and creating comments on individual pages we use the post detailpage (generated by the view action of PostController) Below the post content display

42 Creating and Displaying Comments 35

we display first a list of comments belonging to that post and then a comment creationform

In order to display comments on the post detail page we modify the view script wwwrootblogprotectedviewspostviewphp as follows

ltdiv id=commentsgt

ltphp if($model-gtcommentCountgt=1) gt

lth3gt

ltphp echo $model-gtcommentCount rsquocomment(s)rsquo gt

lth3gt

ltphp $this-gtrenderPartial(rsquo commentsrsquoarray(

rsquopostrsquo=gt$model

rsquocommentsrsquo=gt$model-gtcomments

)) gt

ltphp endif gt

ltdivgt

In the above we call renderPartial() to render a partial view named comments to displaythe list of comments belonging to the current post Note that in the view we use theexpression $model-gtcomments to retrieve the comments for the post This is valid becausewe have declared a comments relation in the Post class Evaluating this expression wouldtrigger an implicit JOIN database query to bring back the proper comments This featureis known as lazy relational query

The partial view comments is not very interesting It mainly goes through every commentand displays the detail of it Interested readers may refer to wwwrootyiidemosblog

protectedpost commentsphp

422 Creating Comments

To handle comment creation we first modify the actionView() method of PostController

as follows

public function actionView()

$post=$this-gtloadModel()

$comment=$this-gtnewComment($post)

$this-gtrender(rsquoviewrsquoarray(

rsquomodelrsquo=gt$post

rsquocommentrsquo=gt$comment

))

36 4 Comment Management

protected function newComment($post)

$comment=new Comment

if(isset($ POST[rsquoCommentrsquo]))

$comment-gtattributes=$ POST[rsquoCommentrsquo]

if($post-gtaddComment($comment))

if($comment-gtstatus==CommentSTATUS PENDING)

Yiiapp()-gtuser-gtsetFlash(rsquocommentSubmittedrsquorsquoThank yoursquo)

$this-gtrefresh()

return $comment

In the above we call the newComment() method before we render view In the newComment()

method we generate a Comment instance and check if the comment form is submitted If sowe try to add the comment for the post by calling $post-gtaddComment($comment) If it goesthrough we refresh the post detail page In case the comment needs to be approved we willshow a flash message to indicate this decision A flash message is usually a confirmationmessage displayed to end users If the user clicks on the refresh button of his browser themessage will disappear

We also need to modify wwwrootblogprotectedviewspostviewphp furthermore

ltdiv id=commentsgt

lth3gtLeave a Commentlth3gt

ltphp if(Yiiapp()-gtuser-gthasFlash(rsquocommentSubmittedrsquo)) gt

ltdiv class=successgt

ltphp echo Yiiapp()-gtuser-gtgetFlash(rsquocommentSubmittedrsquo) gt

ltdivgt

ltphp else gt

ltphp $this-gtrenderPartial(rsquocomment formrsquoarray(

rsquomodelrsquo=gt$comment

)) gt

ltphp endif gt

ltdivgtlt-- comments --gt

43 Managing Comments 37

In the above code we display the flash message if it is available If not we displaythe comment input form by rendering the partial view wwwrootblogprotectedviews

comment formphp

43 Managing Comments

Comment management includes updating deleting and approving comments These op-erations are implemented as actions in the CommentController class

431 Updating and Deleting Comments

The code generated by yiic for updating and deleting comments remains largely un-changed

432 Approving Comments

When comments are newly created they are in pending approval status and need to beapproved in order to be visible to guest users Approving a comment is mainly aboutchanging the status column of the comment

We create an actionApprove() method in CommentController as follows

public function actionApprove()

if(Yiiapp()-gtrequest-gtisPostRequest)

$comment=$this-gtloadModel()

$comment-gtapprove()

$this-gtredirect(array(rsquoindexrsquo))

else

throw new CHttpException(400rsquoInvalid requestrsquo)

In the above when the approve action is invoked via a POST request we call the approve()

method defined in the Comment model to change the status We then redirect the userbrowser to the page displaying the post that this comment belongs to

We also modify the actionIndex() method of Comment to show all comments We wouldlike to see comments pending approved to show up first

public function actionIndex()

38 4 Comment Management

$dataProvider=new CActiveDataProvider(rsquoCommentrsquo array(

rsquocriteriarsquo=gtarray(

rsquowithrsquo=gtrsquopostrsquo

rsquoorderrsquo=gtrsquotstatus tcreate time DESCrsquo

)

))

$this-gtrender(rsquoindexrsquoarray(

rsquodataProviderrsquo=gt$dataProvider

))

Notice that in the above code because both tbl post and tbl comment have columns statusand create time we need to disambiguate the corresponding column reference by prefixingthem with table alias names As described in the guide the alias for the primary table ina relational query is always t Therefore we are prefixing t to the status and create time

columns in the above code

Like the post index view the index view for CommentController uses [CListView] to displaythe comment list which in turn uses the partial view wwwrootblogprotectedviews

comment viewphp to display the detail of each individual comment We will not go intodetails here Interested readers may refer to the corresponding file in the blog demowwwrootyiidemosblogprotectedviewscomment viewphp

Chapter 5

Portlets

51 Customizing Page Layout

Features like rdquothe most recent commentsrdquo rdquotag cloudrdquo are better to be implemented inportlets A portlet is a pluggable user interface component that renders a fragment ofHTML code In this section we describe how to set up the portlet architecture for ourblog application

Before we go into details about developing portlets we first need to adjust our page layoutso that the portlets can be placed in a side bar

A page layout determines how to share common sections (eg header footer) amongdifferent pages and how to embed contents that are specific for different pages In Yiia page layout is represented as a view under the protectedviewslayouts folder Whenwe call the render() method in a controller the rendering result will be automaticallyembedded into this layout view before it is displayed to end users The default layoutview is named as main which means the wwwrootblogprotectedviewslayoutsmain

php file in our blog application

We could directly modify the main layout to add a side bar However doing so wouldmake some irrelevant pages such as about contact pages to have a side bar which is notalways desirable For this reason we need different layouts for different pages

We thus create two different layouts column1 and column2 The former displays the pagein a single column while the latter displays two columns in the page one of which is theside bar We will use column2 layout for the post and comment pages and column1 forthe rest of the pages Because both column1 and column2 still share the same header andfooter sections we let them to share the main layout

Based on the above description we create the column1 layout as follows

ltphp $this-gtbeginContent(rsquolayoutsmainrsquo) gt

ltdiv class=containergt

40 5 Portlets

ltdiv id=contentgt

ltphp echo $content gt

ltdivgtlt-- content --gt

ltdivgt

ltphp $this-gtendContent() gt

and the column2 layout as follows

ltphp $this-gtbeginContent(rsquolayoutsmainrsquo) gt

ltdiv class=containergt

ltdiv class=span-18gt

ltdiv id=contentgt

ltphp echo $content gt

ltdivgtlt-- content --gt

ltdivgt

ltdiv class=span-6 lastgt

ltdiv id=sidebargt

portlets to be placed here

ltdivgtlt-- sidebar --gt

ltdivgt

ltdivgt

ltphp $this-gtendContent() gt

As we can see both column1 and column2 are enclosed in a pair of beginContent andendContent calls They essentially use the specified view layoutsmain to decorate theenclosed content In all these layout views the variable $content refers to the dynamiccontent to be embedded into the layout

Besides customizing the layout view file we also need to adjust the CSS file wwwroot

blogcssmaincss so that the overall appearance would look like what we see in the blogdemo We will not go into details here

Info The skeleton application generated by the yiic tool uses the BluePrint CSSframework It provides a nice font reset and a robust grid system For more detailson how to use this framework please refer to its documentation

After creating the new layouts we need to set the layout property of controllers to makeuse of them So far we have three controllers SiteController PostController andCommentController We set SiteController to use column1 as the default layout whilethe rest two of the controllers use column2 for example

class PostController extends Controller

52 Creating User Menu Portlet 41

public $layout=rsquocolumn2rsquo

Tip If some view of the controller does not use the same layout as declaredby the layout property it may customize it in the view by a simple assignment$this-gtlayout=rsquonewlayoutrsquo

52 Creating User Menu Portlet

Based on the requirements analysis we need three different portlets the rdquouser menurdquoportlet the rdquotag cloudrdquo portlet and the rdquorecent commentsrdquo portlet We will develop theseportlets by extending the [CPortlet] widget provided by Yii

In this section we will develop our first concrete portlet - the user menu portlet whichdisplays a list of menu items that are only available to authenticated users The menucontains four items

bull Approve Comments a hyperlink that leads to a list of comments pending approval

bull Create New Post a hyperlink that leads to the post creation page

bull Manage Posts a hyperlink that leads to the post management page

bull Logout a link button that would log out the current user

521 Creating UserMenu Class

We create the UserMenu class to represent the logic part of the user menu portlet Theclass is saved in the file wwwrootblogprotectedcomponentsUserMenuphp which has thefollowing content

Yiiimport(rsquoziiwidgetsCPortletrsquo)

class UserMenu extends CPortlet

public function init()

$this-gttitle=CHtmlencode(Yiiapp()-gtuser-gtname)

parentinit()

42 5 Portlets

protected function renderContent()

$this-gtrender(rsquouserMenursquo)

The UserMenu class extends from the CPortlet class that we created previously It overridesboth the init() method and the renderContent() method of CPortlet The former setsthe portlet title to be the name of the current user the latter generates the portlet bodycontent by rendering a view named userMenu

Tip Notice that we have to explicitly include the CPortlet class by calling Yii

import() before we refer to it the first time This is because CPortlet is part of thezii project ndash the official extension library for Yii For performance considerationclasses in this project are not listed as core classes Therefore we have to import itbefore we use it the first time

522 Creating userMenu View

Next we create the userMenu view which is saved in the file wwwrootblogprotected

componentsviewsuserMenuphp

ltulgt

ltligtltphp echo CHtmllink(rsquoCreate New Postrsquoarray(rsquopostcreatersquo)) gtltligt

ltligtltphp echo CHtmllink(rsquoManage Postsrsquoarray(rsquopostadminrsquo)) gtltligt

ltligtltphp echo CHtmllink(rsquoApprove Commentsrsquoarray(rsquocommentindexrsquo))

rsquo (rsquo Commentmodel()-gtpendingCommentCount rsquo)rsquo gtltligt

ltligtltphp echo CHtmllink(rsquoLogoutrsquoarray(rsquositelogoutrsquo)) gtltligt

ltulgt

Info By default view files for a widget should be placed under the views sub-directory of the directory containing the widget class file The file name must bethe same as the view name

523 Using UserMenu Portlet

It is time for us to make use of our newly completed UserMenu portlet We modify thelayout view file wwwrootblogprotectedviewslayoutscolumn2php as follows

53 Creating Tag Cloud Portlet 43

ltdiv id=sidebargt

ltphp if(Yiiapp()-gtuser-gtisGuest) $this-gtwidget(rsquoUserMenursquo) gt

ltdivgt

In the above we call the widget() method to generate and execute an instance of theUserMenu class Because the portlet should only be displayed to authenticated users weonly call widget() when the isGuest property of the current user is false (meaning theuser is authenticated)

524 Testing UserMenu Portlet

Letrsquos test what we have so far

1 Open a browser window and enter the URL httpwwwexamplecomblogindex

php Verify that there is nothing displayed in the side bar section of the page

2 Click on the Login hyperlink and fill out the login form to login If successful verifythat the UserMenu portlet appears in the side bar and the portlet has the usernameas its title

3 Click on the rsquoLogoutrsquo hyperlink in the UserMenu portlet Verify that the logout actionis successful and the UserMenu portlet disappears

525 Summary

What we have created is a portlet that is highly reusable We can easily reuse it in adifferent project with little or no modification Moreover the design of this portlet followsclosely the philosophy that logic and presentation should be separated While we did notpoint this out in the previous sections such practice is used nearly everywhere in a typicalYii application

53 Creating Tag Cloud Portlet

Tag cloud displays a list of post tags with visual decorations hinting the popularity of eachindividual tag

531 Creating TagCloud Class

We create the TagCloud class in the file wwwrootblogprotectedcomponentsTagCloud

php The file has the following content

44 5 Portlets

Yiiimport(rsquoziiwidgetsCPortletrsquo)

class TagCloud extends CPortlet

public $title=rsquoTagsrsquo

public $maxTags=20

protected function renderContent()

$tags=Tagmodel()-gtfindTagWeights($this-gtmaxTags)

foreach($tags as $tag=gt$weight)

$link=CHtmllink(CHtmlencode($tag) array(rsquopostindexrsquorsquotagrsquo=gt$tag))

echo CHtmltag(rsquospanrsquo array(

rsquoclassrsquo=gtrsquotagrsquo

rsquostylersquo=gtfont-size$weightpt) $link)n

Unlike the UserMenu portlet the TagCloud portlet does not use a view Instead its presen-tation is done in the renderContent() method This is because the presentation does notcontain much HTML tags

We display each tag as a hyperlink to the post index page with the corresponding tagparameter The font size of each tag link is adjusted according to their relative weightamong other tags If a tag has higher frequency value than the other it will have a biggerfont size

532 Using TagCloud Portlet

Usage of the TagCloud portlet is very simple We modify the layout file wwwrootblog

protectedviewslayoutsmainphp as follows

ltdiv id=sidebargt

ltphp if(Yiiapp()-gtuser-gtisGuest) $this-gtwidget(rsquoUserMenursquo) gt

ltphp $this-gtwidget(rsquoTagCloudrsquo array(

rsquomaxTagsrsquo=gtYiiapp()-gtparams[rsquotagCloudCountrsquo]

)) gt

ltdivgt

54 Creating Recent Comments Portlet 45

54 Creating Recent Comments Portlet

In this section we create the last portlet that displays a list of comments recently pub-lished

541 Creating RecentComments Class

We create the RecentComments class in the file wwwrootblogprotectedcomponentsRecentCommentsphp The file has the following content

Yiiimport(rsquoziiwidgetsCPortletrsquo)

class RecentComments extends CPortlet

public $title=rsquoRecent Commentsrsquo

public $maxComments=10

public function getRecentComments()

return Commentmodel()-gtfindRecentComments($this-gtmaxComments)

protected function renderContent()

$this-gtrender(rsquorecentCommentsrsquo)

In the above we invoke the findRecentComments method which is defined in the Comment

class as follows

class Comment extends CActiveRecord

public function findRecentComments($limit=10)

return $this-gtwith(rsquopostrsquo)-gtfindAll(array(

rsquoconditionrsquo=gtrsquotstatus=rsquoselfSTATUS APPROVED

rsquoorderrsquo=gtrsquotcreate time DESCrsquo

rsquolimitrsquo=gt$limit

))

46 5 Portlets

542 Creating recentComments View

The recentComments view is saved in the file wwwrootblogprotectedcomponentsviews

recentCommentsphp It simply displays every comment returned by the RecentComments

getRecentComments() method

543 Using RecentComments Portlet

We modify the layout file wwwrootblogprotectedviewslayoutsmainphp to embedthis last portlet

ltdiv id=sidebargt

ltphp if(Yiiapp()-gtuser-gtisGuest) $this-gtwidget(rsquoUserMenursquo) gt

ltphp $this-gtwidget(rsquoTagCloudrsquo array(

rsquomaxTagsrsquo=gtYiiapp()-gtparams[rsquotagCloudCountrsquo]

)) gt

ltphp $this-gtwidget(rsquoRecentCommentsrsquo array(

rsquomaxCommentsrsquo=gtYiiapp()-gtparams[rsquorecentCommentCountrsquo]

)) gt

ltdivgt

Chapter 6

Final Work

61 Beautifying URLs

The URLs linking various pages of our blog application currently look ugly For examplethe URL for the page showing a post looks like the following

indexphpr=postshowampid=1

In this section we describe how to beautify these URLs and make them SEO-friendlyOur goal is to be able to use the following URLs in the application

1 indexphppostsyii leads to the page showing a list of posts with tag yii

2 indexphppost2A+Test+Post leads to the page showing the detail of the postwith ID 2 whose title is A Test Post

3 indexphppostupdate1 leads to the page that allows updating the post with ID1

Note that in the second URL format we include the post title in the URL This is mainlyto make the URL SEO friendly It is said that search engines may also respect the wordsfound in a URL when it is being indexed

To achieve our goal we modify the application configuration as follows

return array(

rsquocomponentsrsquo=gtarray(

rsquourlManagerrsquo=gtarray(

rsquourlFormatrsquo=gtrsquopathrsquo

rsquorulesrsquo=gtarray(

48 6 Final Work

rsquopostltidd+gtlttitlegtrsquo=gtrsquopostviewrsquorsquopostslttaggtrsquo=gtrsquopostindexrsquo

)

)

)

)

In the above we configure the urlManager component by setting its urlFormat propertyto be path and adding a set of rules

The rules are used by urlManager to parse and create the URLs in the desired formatFor example the first rule says that if a URL indexphppostsyii is requested theurlManager component should be responsible to dispatch the request to the route post

index and generate a tag GET parameter with the value yii On the other hand whencreating a URL with the route postindex and parameter tag the urlManager componentwill also use this rule to generate the desired URL indexphppostsyii For this reasonwe say that urlManager is a two-way URL manager

The urlManager component can further beautify our URLs such as hiding indexphp inthe URLs appending suffix like html to the URLs We can obtain these features easilyby configuring various properties of urlManager in the application configuration For moredetails please refer to the Guide

62 Logging Errors

A production Web application often needs sophisticated logging for various events In ourblog application we would like to log the errors occurring when it is being used Sucherrors could be programming mistakes or usersrsquo misuse of the system Logging these errorswill help us to improve the blog application

We enable the error logging by modifying the application configuration as follows

return array(

rsquopreloadrsquo=gtarray(rsquologrsquo)

rsquocomponentsrsquo=gtarray(

rsquologrsquo=gtarray(

rsquoclassrsquo=gtrsquoCLogRouterrsquo

rsquoroutesrsquo=gtarray(

array(

rsquoclassrsquo=gtrsquoCFileLogRoutersquo

rsquolevelsrsquo=gtrsquoerror warningrsquo

63 Final Tune-up and Deployment 49

)

)

)

)

)

With the above configuration if an error or warning occurs detailed information will belogged and saved in a file located under the directory wwwrootblogprotectedruntime

The log component offers more advanced features such as sending log messages to a listof email addresses displaying log messages in JavaScript console window etc For moredetails please refer to the Guide

63 Final Tune-up and Deployment

We are close to finish our blog application Before deployment we would like to do sometune-ups

631 Changing Home Page

We change to use the post list page as the home page We modify the application config-uration as follows

return array(

rsquodefaultControllerrsquo=gtrsquopostrsquo

)

Tip Because PostController already declares list to be its default action whenwe access the home page of the application we will see the result generated by thelist action of the post controller

632 Enabling Schema Caching

Because ActiveRecord relies on the metadata about tables to determine the column infor-mation it takes time to read the metadata and analyze it This may not be a problemduring development stage but for an application running in production mode it is a totalwaste of time if the database schema does not change Therefore we should enable theschema caching by modifying the application configuration as follows

50 6 Final Work

return array(

rsquocomponentsrsquo=gtarray(

rsquocachersquo=gtarray(

rsquoclassrsquo=gtrsquoCDbCachersquo

)

rsquodbrsquo=gtarray(

rsquoclassrsquo=gtrsquosystemdbCDbConnectionrsquo

rsquoconnectionStringrsquo=gtrsquosqlitewwwrootblogprotecteddatablogdbrsquo

rsquoschemaCachingDurationrsquo=gt3600

)

)

)

In the above we first add a cache component which uses a default SQLite database as thecaching storage If our server is equipped with other caching extensions such as APCwe could change to use them as well We also modify the db component by setting itsschemaCachingDuration property to be 3600 which means the parsed database schemadata can remain valid in cache for 3600 seconds

633 Disabling Debugging Mode

We modify the entry script file wwwrootblogindexphp by removing the line definingthe constant YII DEBUG This constant is useful during development stage because it allowsYii to display more debugging information when an error occurs However when theapplication is running in production mode displaying debugging information is not agood idea because it may contain sensitive information such as where the script file islocated and the content in the file etc

634 Deploying the Application

The final deployment process manly involves copying the directory wwwrootblog to thetarget directory The following checklist shows every needed step

1 Install Yii in the target place if it is not available

2 Copy the entire directory wwwrootblog to the target place

3 Edit the entry script file indexphp by pointing the $yii variable to the new Yiibootstrap file

4 Edit the file protectedyiicphp by setting the $yiic variable to be the new Yiiyiicphp file

64 Future Enhancements 51

5 Change the permission of the directories assets and protectedruntime so that theyare writable by the Web server process

64 Future Enhancements

641 Using a Theme

Without writing any code our blog application is already themeable To use a themewe mainly need to develop the theme by writing customized view files in the theme Forexample to use a theme named classic that uses a different page layout we would createa layout view file wwwrootblogthemesclassicviewslayoutsmainphp We also needto change the application configuration to indicate our choice of the classic theme

return array(

rsquothemersquo=gtrsquoclassicrsquo

)

642 Internationalization

We may also internationalize our blog application so that its pages can be displayed indifferent languages This mainly involves efforts in two aspects

First we may create view files in different languages For example for the list pageof PostController we can create a view file wwwrootblogprotectedviewspostzh cn

listphp When the application is configured to use simplified Chinese (the language codeis zh cn) Yii will automatically use this new view file instead of the original one

Second we may create message translations for those messages generated by code Themessage translations should be saved as files under the directory wwwrootblogprotected

messages We also need to modify the code where we use text strings by enclosing themin the method call Yiit()

For more details about internationalization please refer to the Guide

643 Improving Performance with Cache

While the Yii framework itself is very efficient it is not necessarily true that an applicationwritten in Yii efficient There are several places in our blog application that we can improvethe performance For example the tag clould portlet could be one of the performancebottlenecks because it involves complex database query and PHP logic

52 6 Final Work

We can make use of the sophisticated caching feature provided by Yii to improve theperformance One of the most useful components in Yii is COutputCache which caches afragment of page display so that the underlying code generating the fragment does not needto be executed for every request For example in the layout file wwwrootblogprotected

viewslayoutsmainphp we can enclose the tag cloud portlet with COutputCache

ltphp if($this-gtbeginCache(rsquotagCloudrsquo array(rsquodurationrsquo=gt3600))) gt

ltphp $this-gtwidget(rsquoTagCloudrsquo array(

rsquomaxTagsrsquo=gtYiiapp()-gtparams[rsquotagCloudCountrsquo]

)) gt

ltphp $this-gtendCache() gt

With the above code the tag cloud display will be served from cache instead of beinggenerated on-the-fly for every request The cached content will remain valid in cache for3600 seconds

644 Adding New Features

Our blog application only has very basic functionalities To become a complete blogsystem more features are needed for example calendar portlet email notifications postcategorization archived post portlet and so on We will leave the implementation of thesefeatures to interested readers

  • Contents
  • License
  • Getting Started
    • Building a Blog System using Yii
    • Testdriving with Yii
      • Installing Yii
      • Creating Skeleton Application
      • Application Workflow
        • Requirements Analysis
        • Overall Design
          • Initial Prototyping
            • Setting Up Database
              • Creating Database
              • Establishing Database Connection
                • Scaffolding
                • Authenticating User
                • Summary
                  • Post Management
                    • Customizing Post Model
                      • Customizing rules() Method
                      • Customizing relations() Method
                      • Representing Status in Text
                        • Creating and Updating Posts
                          • Customizing Access Control
                          • Customizing create and update Operations
                            • Displaying Posts
                              • Customizing view Operation
                              • Customizing index Operation
                                • Managing Posts
                                  • Listing Posts in Tabular View
                                  • Deleting Posts
                                      • Comment Management
                                        • Customizing Comment Model
                                          • Customizing rules() Method
                                          • Customizing attributeLabels() Method
                                          • Customizing Saving Process
                                            • Creating and Displaying Comments
                                              • Displaying Comments
                                              • Creating Comments
                                                • Managing Comments
                                                  • Updating and Deleting Comments
                                                  • Approving Comments
                                                      • Portlets
                                                        • Customizing Page Layout
                                                        • Creating User Menu Portlet
                                                          • Creating UserMenu Class
                                                          • Creating userMenu View
                                                          • Using UserMenu Portlet
                                                          • Testing UserMenu Portlet
                                                          • Summary
                                                            • Creating Tag Cloud Portlet
                                                              • Creating TagCloud Class
                                                              • Using TagCloud Portlet
                                                                • Creating Recent Comments Portlet
                                                                  • Creating RecentComments Class
                                                                  • Creating recentComments View
                                                                  • Using RecentComments Portlet
                                                                      • Final Work
                                                                        • Beautifying URLs
                                                                        • Logging Errors
                                                                        • Final Tune-up and Deployment
                                                                          • Changing Home Page
                                                                          • Enabling Schema Caching
                                                                          • Disabling Debugging Mode
                                                                          • Deploying the Application
                                                                            • Future Enhancements
                                                                              • Using a Theme
                                                                              • Internationalization
                                                                              • Improving Performance with Cache
                                                                              • Adding New Features
Page 11: Yii Blog 1.1.0 Demo Tutor

4 1 Getting Started

7 The actionContact method renders a view named contact to the Web user Inter-nally this is achieved by including the view file wwwrootblogprotectedviews

sitecontactphp and embedding the result into the layout file wwwrootblog

protectedviewslayoutsmainphp

13 Requirements Analysis

The blog system that we are going to develop is a single user system The owner of thesystem will be able to perform the following actions

bull Login and logout

bull Create update and delete posts

bull Publish unpublish and archive posts

bull Approve and delete comments

All other users are guest users who can perform the following actions

bull Read posts

bull Create comments

Additional Requirements for this system include

bull The homepage of the system should display a list of the most recent posts

bull If a page contains more than 10 posts they should be displayed in pages

bull The system should display a post together with its comments

bull The system should be able to list posts with a specified tag

bull The system should show a cloud of tags indicating their use frequencies

bull The system should show a list of most recent comments

bull The system should be themeable

bull The system should use SEO-friendly URLs

14 Overall Design 5

14 Overall Design

Based on the analysis of the requirements we decide to use the following database tablesto store the persistent data for our blog application

bull tbl user stores the user information including username and password

bull tbl post stores the blog post information It mainly consists of the following columns

ndash title required title of the post

ndash content required body content of the post which uses the Markdown format

ndash status required status of the post which can be one of following values

lowast 1 meaning the post is in draft and is not visible to public

lowast 2 meaning the post is published to public

lowast 3 meaning the post is outdated and is not visible in the post list (stillaccessible individually though)

ndash tags optional a list of comma-separated words categorizing the post

bull tbl comment stores the post comment information Each comment is associated witha post and mainly consists of the following columns

ndash name required the author name

ndash email required the author email

ndash website optional the author website URL

ndash content required the comment content in plain text format

ndash status required status of the comment which indicates whether the commentis approved (value 2) or not (value 1)

bull tbl tag stores post tag frequency information that is needed to implement the tagcloud feature The table mainly contains the following columns

ndash name required the unique tag name

ndash frequency required the number of times that the tag appears in posts

bull tbl lookup stores generic lookup information It is essentially a map between integervalues and text strings The former is the data representation in our code while thelatter is the corresponding presentation to end users For example we use integer1 to represent the draft post status and string Draft to display this status to endusers This table mainly contains the following columns

6 1 Getting Started

ndash name the textual representation of the data item that is to be displayed to endusers

ndash code the integer representation of the data item

ndash type the type of the data item

ndash position the relative display order of the data item among other items of thesame type

The following entity-relation (ER) diagram shows the table structure and relationshipsabout the above tables

Figure 11 Entity-Relation Diagram of the Blog Database

Complete SQL statements corresponding to the above ER diagram may be found inthe blog demo In our Yii installation they are in the file wwwrootyiidemosblog

protecteddataschemasqlitesql

Info We name all our table names and column names in lower case This is becausedifferent DBMS often have different case-sensitivity treatment and we want to avoidtroubles like this

We also prefix all our tables with tbl This serves for two purposes First the prefixintroduces a namespace to these tables in case when they need to coexist with othertables in the same database which often happens in a shared hosting environmentwhere a single database is being used by multiple applications Second using tableprefix reduces the possibility of having some table names that are reserved keywordsin DBMS

14 Overall Design 7

We divide the development of our blog application into the following milestones

bull Milestone 1 creating a prototype of the blog system It should consist of most ofthe required functionalities

bull Milestone 2 completing post management It includes creating listing showingupdating and deleting posts

bull Milestone 3 completing comment management It includes creating listing ap-proving updating and deleting post comments

bull Milestone 4 implementing portlets It includes user menu login tag cloud andrecent comments portlets

bull Milestone 5 final tune-up and deployment

8 1 Getting Started

Chapter 2

Initial Prototyping

21 Setting Up Database

Having created a skeleton application and finished the database design in this section wewill create the blog database and establish the connection to it in the skeleton application

211 Creating Database

We choose to create a SQLite database Because the database support in Yii is built on topof PDO we can easily switch to use a different type of DBMS (eg MySQL PostgreSQL)without the need to change our application code

We create the database file blogdb under the directory wwwrootblogprotecteddataNote that both the directory and the database file have to be writable by the Web serverprocess as required by SQLite We may simply copy the database file from the blogdemo in our Yii installation which is located at wwwrootyiidemosblogprotecteddatablogdb We may also generate the database by executing the SQL statements in the filewwwrootyiidemosblogprotecteddataschemasqlitesql

Tip To execute SQL statements we may use the sqlite3 command line tool thatcan be found in the SQLite official website

212 Establishing Database Connection

To use the blog database in the skeleton application we created we need to modify itsapplication configuration which is stored in the PHP script wwwrootblogprotected

configmainphp The script returns an associative array consisting of name-value pairseach of which is used to initialize a writable property of the application instance

We configure the db component as follows

10 2 Initial Prototyping

return array(

rsquocomponentsrsquo=gtarray(

rsquodbrsquo=gtarray(

rsquoconnectionStringrsquo=gtrsquosqlitewwwrootblogprotecteddatablogdbrsquo

rsquotablePrefixrsquo=gtrsquotbl rsquo

)

)

)

The above configuration says that we have a db application component whose connectionStringproperty should be initialized as sqlitewwwrootblogprotecteddatablogdb and whosetablePrefix property should be tbl

With this configuration we can access the DB connection object using Yiiapp()-gtdb

at any place in our code Note that Yiiapp() returns the application instance that wecreate in the entry script If you are interested in possible methods and properties that theDB connection has you may refer to its class reference However in most cases we are notgoing to use this DB connection directly Instead we will use the so-called ActiveRecordto access the database

We would like to explain a bit more about the tablePrefix property that we set in theconfiguration This tells the db connection that it should respect the fact we are usingtbl as the prefix to our database table names In particular if in a SQL statement thereis a token enclosed within double curly brackets (eg post then the db connectionshould translate it into a name with the table prefix (eg tbl post) before sending it toDBMS for execution This feature is especially useful if in future we need to modify thetable name prefix without touching our source code For example if we are developing ageneric content management system (CMS) we may exploit this feature so that when itis being installed in a new environment we can allow users to choose a table prefix theylike

22 Scaffolding 11

Tip If you want to use MySQL instead of SQLite to store data you may cre-ate a MySQL database named blog using the SQL statements in wwwrootyii

demosblogprotecteddataschemamysqlsql Then modify the applicationconfiguration as follows

return array(

rsquocomponentsrsquo=gtarray(

rsquodbrsquo=gtarray(

rsquoconnectionStringrsquo =gt rsquomysqlhost=localhostdbname=blogrsquo

rsquoemulatePreparersquo =gt true

rsquousernamersquo =gt rsquorootrsquo

rsquopasswordrsquo =gt rsquorsquo

rsquocharsetrsquo =gt rsquoutf8rsquo

rsquotablePrefixrsquo =gt rsquotbl rsquo

)

)

)

22 Scaffolding

Create read update and delete (CRUD) are the four basic operations of data objects in anapplication Because the task of implementing the CRUD operations is so common whendeveloping Web applications Yii provides a tool to automate this process (also known asscaffolding) In this section we will describe how to use the tool to implement CRUD forposts and comments

Open a command window and run the following commands

wwwrootyiiframeworkyiic shell wwwrootblogindexphp

Yii Interactive Tool v11

Please type rsquohelprsquo for help Type rsquoexitrsquo to quit

gtgt model

gtgt crud Post

gtgt crud Comment

gtgt exit

12 2 Initial Prototyping

Info Some PHP installations may use a different phpini file for command line(CLI) PHP parser As a result when running the above yiic commands youmay encounter errors like rdquoYiiBaseinclude(PDOphp) failed to open streamrdquoor rdquocould not find driverrdquo Please double check your CLI PHP configuration byexecuting the following command

php -r phpinfo()

The result of the above command will show which phpini file is being used andwhich extensions are loaded If a wrong phpini file is used you may use thefollowing command to explicitly specify the correct phpini to use

php -c phpini wwwrootyiiframeworkyiicphp shell wwwrootblogindexphp

The commands above accomplish two tasks First the model command generates a modelclass file for each table in the blog database Second the crud commands generate thecode needed by the CRUD operations for the Post and Comment models

Tip The command model generates a model class for every table in the databaseSometimes we probably donrsquot want to do so (eg the database contains some irrel-evant tables) In this case we can create model classes one by one For exampleto create the User model we can use the command model User The model com-mand also has some more advanced usages For more details execute help model

command

We can test the generated code by accessing the following URLs

httpwwwexamplecomblogindexphpr=post

httpwwwexamplecomblogindexphpr=comment

Notice that the post and comment features implemented by the generated code are com-pletely independent of each other Also when creating a new post or comment we arerequired to enter information such as author id and create time which in real applica-tion should be set by the program Donrsquot worry We will fix these problems in the nextmilestones For now we should be fairly satisfied as this prototype already contains mostfeatures that we need to implement for the blog application

To prepare for the next milestones letrsquos take a closer look at the files generated by theabove commands All the files are generated under wwwrootblogprotected For conve-nience we group them into model files controller files and view files

22 Scaffolding 13

bull model files

ndash modelsUserphp contains the User class that extends from CActiveRecord andcan be used to access the tbl user database table

ndash modelsPostphp contains the Post class that extends from CActiveRecord andcan be used to access the tbl post database table

ndash modelsTagphp contains the Tag class that extends from CActiveRecord andcan be used to access the tbl tag database table

ndash modelsCommentphp contains the Comment class that extends from CActiveRe-cord and can be used to access the tbl comment database table

ndash modelsLookupphp contains the Lookup class that extends from CActiveRecordand can be used to access the tbl lookup database table

bull controller file

ndash controllersPostControllerphp contains the PostController class which is thecontroller in charge of all CRUD operations about posts

ndash controllersCommentControllerphp contains the CommentController class whichis the controller in charge of all CRUD operations about comments

bull view files

ndash viewspostcreatephp is the view file that shows an HTML form to create anew post

ndash viewspostupdatephp is the view file that shows an HTML form to update anexisting post

ndash viewspostviewphp is the view file that displays the detailed information of apost

ndash viewspostindexphp is the view file that displays a list of posts

ndash viewspostadminphp is the view file that displays posts in a table with ad-ministrative commands

ndash viewspost formphp is the partial view file embedded in viewspostcreate

php and viewspostupdatephp It displays the HTML form for collecting postinformation

ndash viewspost viewphp is the partial view file used by viewspostindexphp Itdisplays the brief view of a single post

ndash a similar set of view files are also generated for comment

14 2 Initial Prototyping

In order to understand better how the above files are used we show in the following theworkflow that occurs in the blog application when displaying a list of posts

1 The user requests the URL httpwwwexamplecomblogindexphpr=posts

2 The entry script is executed by the Web server which creates and initializes anapplication instance to handle the request

3 The application creates an instance of PostController and executes it

4 The PostController instance executes the index action by calling its actionIndex()

method Note that index is the default action if the user does not specify an actionto execute in the URL

5 The actionIndex() method queries database to bring back the list of recent posts

6 The actionIndex() method renders the index view with the post data

23 Authenticating User

Our blog application needs to differentiate between the system owner and guest usersTherefore we need to implement the user authentication feature

As you may have found that the skeleton application already provides user authenticationby checking if the username and password are both demo or admin In this section wewill modify the corresponding code so that the authentication is done against the User

database table

User authentication is performed in a class implementing the IUserIdentity interface Theskeleton application uses the UserIdentity class for this purpose The class is stored inthe file wwwrootblogprotectedcomponentsUserIdentityphp

Tip By convention the name of a class file must be the same as the correspondingclass name suffixed with the extension php Following this convention one canrefer to a class using a path alias For example we can refer to the UserIdentity

class with the alias applicationcomponentsUserIdentity Many APIs in Yiican recognize path aliases (eg YiicreateComponent()) and using path aliasesavoids the necessity of embedding absolute file paths in the code The existence ofthe latter often causes trouble when we deploy an application

We modify the UserIdentity class as follows

23 Authenticating User 15

ltphp

class UserIdentity extends CUserIdentity

private $ id

public function authenticate()

$username=strtolower($this-gtusername)

$user=Usermodel()-gtfind(rsquoLOWER(username)=rsquoarray($username))

if($user===null)

$this-gterrorCode=selfERROR USERNAME INVALID

else if($user-gtvalidatePassword($this-gtpassword))

$this-gterrorCode=selfERROR PASSWORD INVALID

else

$this-gt id=$user-gtid

$this-gtusername=$user-gtusername

$this-gterrorCode=selfERROR NONE

return $this-gterrorCode==selfERROR NONE

public function getId()

return $this-gt id

In the authenticate() method we use the User class to look for a row in the User tablewhose username column is the same as the given username in a case-insensitive mannerRemember that the User class was created using the yiic tool in the prior section Becausethe User class extends from CActiveRecord we can exploit the ActiveRecord feature toaccess the User table in an OOP fashion

In order to check if the user has entered a valid password we invoke the validatePassword

method of the User class We need to modify the file wwwrootblogprotectedmodels

Userphp as follows Note that instead of storing the plain password in the database westore the hash result of the password and a randomly generated salt key When validatingthe user-entered password we should compare the hash results instead

class User extends CActiveRecord

public function validatePassword($password)

return $this-gthashPassword($password$this-gtsalt)===$this-gtpassword

16 2 Initial Prototyping

public function hashPassword($password$salt)

return md5($salt$password)

In the UserIdentity class we also override the getId() method which returns the id valueof the user found in the User table The parent implementation would return the usernameinstead Both the username and id properties will be stored in the user session and maybe accessed via Yiiapp()-gtuser from anywhere in our code

Tip In the UserIdentity class we reference the class CUserIdentity without ex-plicitly including the corresponding class file This is because CUserIdentity is acore class provided by the Yii framework Yii will automatically include the classfile for any core class when it is referenced for the first time

We also do the same with the User class This is because the User class file is placedunder the directory wwwrootblogprotectedmodels which has been added tothe PHP include path according to the following lines found in the applicationconfiguration

return array(

rsquoimportrsquo=gtarray(

rsquoapplicationmodelsrsquo

rsquoapplicationcomponentsrsquo

)

)

The above configuration says that any class whose class file is located under either wwwrootblogprotectedmodels or wwwrootblogprotectedcomponents willbe automatically included when the class is referenced for the first time

The UserIdentity class is mainly used by the LoginForm class to authenticate a user basedon the username and password input collected from the login page The following codefragment shows how UserIdentity is used

$identity=new UserIdentity($username$password)

$identity-gtauthenticate()

switch($identity-gterrorCode)

24 Summary 17

case UserIdentityERROR NONE

Yiiapp()-gtuser-gtlogin($identity)

break

Info People often get confused about identity and the user application componentThe former represents a way of performing authentication while the latter is usedto represent the information related with the current user An application can onlyhave one user component but it can have one or several identity classes dependingon what kind of authentication it supports Once authenticated an identity instancemay pass its state information to the user component so that they are globallyaccessible via user

To test the modified UserIdentity class we can browse the URL httpwwwexample

comblogindexphp and try logging in with the username and password that we store inthe User table If we use the database provided by the blog demo we should be able tologin with username demo and password demo Note that this blog system does not providethe user management feature As a result a user cannot change his account or create anew one through the Web interface The user management feature may be considered asa future enhancement to the blog application

24 Summary

We have completed the milestone 1 Letrsquos summarize what we have done so far

1 We identified the requirements to be fulfilled

2 We installed the Yii framework

3 We created a skeleton application

4 We designed and created the blog database

5 We modified the application configuration by adding the database connection

6 We generated the code that implements the basic CRUD operations for both postsand comments

7 We modified the authentication method to check against the tbl user table

18 2 Initial Prototyping

For a new project most of the time will be spent in step 1 and 4 for this first milestone

Although the code generated by the yiic tool implements fully functional CRUD oper-ations for a database table it often needs to be modified in practical applications Forthis reason in the next two milestone our job is to customize the generated CRUD codeabout posts and comments so that it reaches our initial requirements

In general we first modify the model class file by adding appropriate validation rules anddeclaring relational objects We then modify the controller action and view code for eachindividual CRUD operation

Chapter 3

Post Management

31 Customizing Post Model

The Post model class generated by the yiic tool mainly needs to be modified in two places

bull the rules() method specifies the validation rules for the model attributes

bull the relations() method specifies the related objects

Info A model consists of a list of attributes each associated with a column in thecorresponding database table Attributes can be declared explicitly as class membervariables or implicitly without any declaration

311 Customizing rules() Method

We first specify the validation rules which ensure the attribute values entered by usersare correct before they are saved to the database For example the status attribute ofPost should be an integer 1 2 or 3 The yiic tool also generates validation rules for eachmodel However these rules are based on the table column information and may not beappropriate

Based on the requirement analysis we modify the rules() method as follows

public function rules()

return array(

array(rsquotitle content statusrsquo rsquorequiredrsquo)

array(rsquotitlersquo rsquolengthrsquo rsquomaxrsquo=gt128)

array(rsquostatusrsquo rsquoinrsquo rsquorangersquo=gtarray(123))

array(rsquotagsrsquo rsquomatchrsquo rsquopatternrsquo=gtrsquo^[ws]+$rsquorsquomessagersquo=gtrsquoTags can only contain word charactersrsquo)

20 3 Post Management

array(rsquotagsrsquo rsquonormalizeTagsrsquo)

)

In the above we specify that the title content and status attributes are required thelength of title should not exceed 128 the status attribute value should be 1 (draft) 2(published) or 3 (archived) and the tags attribute should only contain word charactersand commas In addition we use normalizeTags to normalize the user-entered tags so thatthe tags are unique and properly separated with commas

The validators such as required length in and match are all built-in validators providedby Yii The normalizeTags validator is a method-based validator that we need to definein the Post class For more information about how to specify validation rules please referto the Guide

public function normalizeTags($attribute$params)

$this-gttags=Tagarray2string(array unique(Tagstring2array($this-gttags)))

The rules declared in this method are executed one by one when we call the validate() orsave() method of the model instance

Note It is very important to remember that attributes appearing in rules() mustbe those to be entered by end users Other attributes such as id and create time

in the Post model which are set by our code or database should not be in rules()For more details please refer to Securing Attribute Assignments

After making these changes we can visit the post creation page again to verify that thenew validation rules are taking effect

312 Customizing relations() Method

Lastly we customize the relations() method to specify the related objects of a post Bydeclaring these related objects in relations() we can exploit the powerful RelationalActiveRecord (RAR) feature to access the related object information of a post such asits author and comments without the need to write complex SQL JOIN statements

We customize the relations() method as follows

31 Customizing Post Model 21

public function relations()

return array(

rsquoauthorrsquo =gt array(selfBELONGS TO rsquoUserrsquo rsquoauthor idrsquo)

rsquocommentsrsquo =gt array(selfHAS MANY rsquoCommentrsquo rsquopost idrsquo

rsquoconditionrsquo=gtrsquocommentsstatus=rsquoCommentSTATUS APPROVED

rsquoorderrsquo=gtrsquocommentscreate time DESCrsquo)

rsquocommentCountrsquo =gt array(selfSTAT rsquoCommentrsquo rsquopost idrsquo

rsquoconditionrsquo=gtrsquostatus=rsquoCommentSTATUS APPROVED)

)

The above relations state that

bull A post belongs to an author whose class is User and the relationship is establishedbased on the author id attribute value of the post

bull A post has many comments whose class is Comment and the relationship is establishedbased on the post id attribute value of the comments These comments should besorted according to their creation time and the comments must be approved

bull The commentCount relation is a bit special as it returns back an aggregation resultwhich is about how many comments the post has

With the above relation declaration we can easily access the author and comments of apost like the following

$author=$post-gtauthor

echo $author-gtusername

$comments=$post-gtcomments

foreach($comments as $comment)

echo $comment-gtcontent

For more details about how to declare and use relations please refer to the Guide

313 Representing Status in Text

Because the status of a post is stored as an integer in the database we need to provide atextual representation so that it is more intuitive when being displayed to end users In alarge system the similar requirement is very common

22 3 Post Management

As a generic solution we use the tbl lookup table to store the mapping between integervalues and textual representations that are needed by other data objects We modify theLookup model class as follows to more easily access the textual data in the table

class Lookup extends CActiveRecord

private static $ items=array()

public static function items($type)

if(isset(self$ items[$type]))

selfloadItems($type)

return self$ items[$type]

public static function item($type$code)

if(isset(self$ items[$type]))

selfloadItems($type)

return isset(self$ items[$type][$code]) self$ items[$type][$code] false

private static function loadItems($type)

self$ items[$type]=array()

$models=selfmodel()-gtfindAll(array(

rsquoconditionrsquo=gtrsquotype=typersquo

rsquoparamsrsquo=gtarray(rsquotypersquo=gt$type)

rsquoorderrsquo=gtrsquopositionrsquo

))

foreach($models as $model)

self$ items[$type][$model-gtcode]=$model-gtname

Our new code mainly provides two static methods Lookupitems() and Lookupitem()The former returns a list of strings belonging to the specified data type while the latterreturns a particular string for the given data type and data value

Our blog database is pre-populated with two lookup types PostStatus and CommentStatusThe former refers to the possible post statuses while the latter the comment statuses

In order to make our code easier to read we also declare a set of constants to representthe status integer values We should use these constants through our code when referringto the corresponding status values

32 Creating and Updating Posts 23

class Post extends CActiveRecord

const STATUS DRAFT=0

const STATUS PUBLISHED=1

const STATUS ARCHIVED=2

Therefore we can call Lookupitems(rsquoPostStatusrsquo) to get the list of possible post statuses(text strings indexed by the corresponding integer values) and call Lookupitem(rsquoPostStatusrsquoPostSTATUS PUBLISHED) to get the string representation of the published status

32 Creating and Updating Posts

With the Post model ready we need to fine-tune the actions and views for the controllerPostController In this section we first customize the access control of CRUD operationswe then modify the code implementing the create and update operations

321 Customizing Access Control

The first thing we want to do is to customize the access control because the code generatedby yiic does not fit our needs

We modify the accessRules() method in the file wwwrootblogprotectedcontrollers

PostControllerphp as follows

public function accessRules()

return array(

array(rsquoallowrsquo allow all users to perform rsquolistrsquo and rsquoshowrsquo actions

rsquoactionsrsquo=gtarray(rsquoindexrsquo rsquoviewrsquo)

rsquousersrsquo=gtarray(rsquorsquo)

)

array(rsquoallowrsquo allow authenticated users to perform any action

rsquousersrsquo=gtarray(rsquorsquo)

)

array(rsquodenyrsquo deny all users

rsquousersrsquo=gtarray(rsquorsquo)

)

)

The above rules state that all users can access the index and view actions and authenti-cated users can access any actions including the admin action The user should be denied

24 3 Post Management

access in any other scenario Note that these rules are evaluated in the order they arelisted here The first rule matching the current context makes the access decision Forexample if the current user is the system owner who tries to visit the post creation pagethe second rule will match and it will give the access to the user

322 Customizing create and update Operations

The create and update operations are very similar They both need to display an HTMLform to collect user inputs validate them and save them into database The main differ-ence is that the update operation will pre-populate the form with the existing post datafound in the database For this reason the yiic tool generates a partial view wwwroot

blogprotectedviewspost formphp that is embedded in both the create and update

views to render the needed HTML form

We first change the formphp file so that the HTML form only collects the inputs wewant title content tags and status We use plain text fields to collect inputs for thefirst three attributes and a dropdown list to collect input for status The dropdown listoptions are the text displays of the possible post statuses

ltphp echo CHtmlactiveDropDownList($postrsquostatusrsquoLookupitems(rsquoPostStatusrsquo)) gt

In the above we call Lookupitems(rsquoPostStatusrsquo) to bring back the list of post statuses

We then modify the Post class so that it can automatically set some attributes (eg create

time author id) before a post is saved to the database We override the beforeSave()

method as follows

protected function beforeSave()

if(parentbeforeSave())

if($this-gtisNewRecord)

$this-gtcreate time=$this-gtupdate time=time()

$this-gtauthor id=Yiiapp()-gtuser-gtid

else

$this-gtupdate time=time()

return true

else

return false

33 Displaying Posts 25

When we save a post we want to update the tbl tag table to reflect the change of tagfrequencies We can do this work in the afterSave() method which is automaticallyinvoked by Yii after a post is successfully saved into the database

protected function afterSave()

parentafterSave()

Tagmodel()-gtupdateFrequency($this-gt oldTags $this-gttags)

private $ oldTags

protected function afterFind()

parentafterFind()

$this-gt oldTags=$this-gttags

In the implementation because we want to detect if the user changes the tags in case he isupdating an existing post we need to know what the old tags are For this reason we alsowrite the afterFind() method to keep the old tags in the variable oldTags The methodafterFind() is invoked automatically by Yii when an AR record is populated with thedata from database

We are not going to give details of the TagupdateFrequency() method here Interestedreaders may refer to the file wwwrootyiidemosblogprotectedmodelsTagphp

33 Displaying Posts

In our blog application a post may be displayed among a list of posts or by itself Theformer is implemented as the index operation while the latter the view operation In thissection we customize both operations to fulfill our initial requirements

331 Customizing view Operation

The view operation is implemented by the actionView() method in PostController Itsdisplay is generated by the view view with the view file wwwrootblogprotectedviews

postviewphp

Below is the relevant code implementing the view operation in PostController

public function actionView()

26 3 Post Management

$this-gtrender(rsquoviewrsquoarray(

$post=$this-gtloadModel()

$this-gtrender(rsquoviewrsquoarray(

rsquomodelrsquo=gt$post

))

))

private $ model

public function loadModel()

if($this-gt model===null)

if(isset($ GET[rsquoidrsquo]))

if(Yiiapp()-gtuser-gtisGuest)

$condition=rsquostatus=rsquoPostSTATUS PUBLISHED

rsquo OR status=rsquoPOSTSTATUS ARCHIVED

else

$condition=rsquorsquo

$this-gt model=Postmodel()-gtfindbyPk($ GET[rsquoidrsquo] $condition)

if($this-gt model===null)

throw new CHttpException(404rsquoThe requested page does not existrsquo)

return $this-gt model

Our change mainly lies in the loadModel() method In this method we query the Post

table according to the id GET parameter If the post is not found or if it is not publishedor archived (when the user is a guest) we will throw a 404 HTTP error Otherwise thepost object is returned to actionView() which in turn passes the post object to the viewscript for further display

Tip Yii captures HTTP exceptions (instances of CHttpException) and displaysthem in either predefined templates or customized error views The skeleton appli-cation generated by yiic already contains a customized error view in wwwroot

blogprotectedviewssiteerrorphp We can modify this file if we want tofurther customize the error display

The change in the view script is mainly about ajdusting the formatting and styles of thepost display We will not go into details here Interested readers may refer to wwwroot

blogprotectedviewspostviewphp

33 Displaying Posts 27

332 Customizing index Operation

Like the view operation we customize the index operation in two places the actionIndex()

method in PostController and the view file wwwrootblogprotectedviewspostindex

php We mainly need to add the support for displaying a list of posts that are associatedwith a specified tag

Below is the modified actionIndex() method in PostController

public function actionIndex()

$criteria=new CDbCriteria(array(

rsquoconditionrsquo=gtrsquostatus=rsquoPostSTATUS PUBLISHED

rsquoorderrsquo=gtrsquoupdate time DESCrsquo

rsquowithrsquo=gtrsquocommentCountrsquo

))

if(isset($ GET[rsquotagrsquo]))

$criteria-gtaddSearchCondition(rsquotagsrsquo$ GET[rsquotagrsquo])

$dataProvider=new CActiveDataProvider(rsquoPostrsquo array(

rsquopaginationrsquo=gtarray(

rsquopageSizersquo=gtYiiapp()-gtparams[rsquopostsPerPagersquo]

)

rsquocriteriarsquo=gt$criteria

))

$this-gtrender(rsquoindexrsquoarray(

rsquodataProviderrsquo=gt$dataProvider

))

In the above we first create a query criteria for retrieving post list The criteria statesthat only published posts should be returned and they should be sorted according to theirupdate time in descending order Because when displaying a post in the list we want toshow how many comments the post has received in the criteria we also specify to bringback commentCount which if you remember is a relation declared in Postrelations()

In case when a user wants to see posts with a specific tag we would add a search conditionto the criteria to look for the specified tag

Using the query criteria we create a data provider which mainly serves for three purposesFirst it does pagination of the data when too many results may be returned Herewe customize the pagination by setting the page size to be an application parameterpostsPerPage Second it does sorting according to the user request And finally it feedsthe paginated and sorted data to widgets or view code for presentation

28 3 Post Management

After we finish with actionIndex() we modify the index view as follows Our change ismainly about adding the h1 header when the user specifies to display posts with a tag

ltphp if(empty($ GET[rsquotagrsquo])) gt

lth1gtPosts Tagged with ltigtltphp echo CHtmlencode($ GET[rsquotagrsquo]) gtltigtlth1gt

ltphp endif gt

ltphp $this-gtwidget(rsquoziiwidgetsCListViewrsquo array(

rsquodataProviderrsquo=gt$dataProvider

rsquoitemViewrsquo=gtrsquo viewrsquo

rsquotemplatersquo=gtitemsnpager)) gt

Note that in the above we use [CListView] to display the post list This widget requires apartial view to display the detail of each individual post Here we specify the partial viewto be view which means the file wwwrootblogprotectedviewspost viewphp In thisview script we can acccess the post instance being displayed via a local variable named$data

34 Managing Posts

Managing posts mainly refers to listing posts in an administrative view that allows us tosee posts with all statuses updating them and deleting them They are accomplished bythe admin operation and the delete operation respectively The code generated by yiic

does not need much modification Below we mainly explain how these two operations areimplemented

341 Listing Posts in Tabular View

The admin operation shows posts with all statuses in a tabular view The view supportssorting and pagination The following is the actionAdmin() method in PostController

public function actionAdmin()

$dataProvider=new CActiveDataProvider(rsquoPostrsquo array(

rsquosortrsquo=gtarray(

rsquodefaultOrderrsquo=gtrsquostatus update time DESCrsquo

)

))

$this-gtrender(rsquoadminrsquoarray(

rsquodataProviderrsquo=gt$dataProvider

))

34 Managing Posts 29

The above code is very similar to that in actionIndex() The main difference is in thequery criteria Here we do not specify any particular query criteria since we want to seeall posts We do customize the sort property of the data provider so that by default theposts are shown in the order of their statuses and update time

Below is the code for the admin view

lth1gtManage Postslth1gt

ltphp $this-gtwidget(rsquoziiwidgetsgridCGridViewrsquo array(

rsquodataProviderrsquo=gt$dataProvider

rsquocolumnsrsquo=gtarray(

array(

rsquonamersquo=gtrsquotitlersquo

rsquotypersquo=gtrsquorawrsquo

rsquovaluersquo=gtrsquoCHtmllink(CHtmlencode($data-gttitle) $data-gturl)rsquo

)

array(

rsquonamersquo=gtrsquostatusrsquo

rsquovaluersquo=gtrsquoLookupitem(PostStatus$data-gtstatus)rsquo

)

rsquocreate timedatetimersquo

rsquoupdate timedatetimersquo

array(

rsquoclassrsquo=gtrsquoCButtonColumnrsquo

)

)

)) gt

We use [CGridView] to display the posts It allows us to sort by a column and paginatethrough the posts if there are too many to be displayed in a single page Our change ismainly about how to display each column For example for the title column we specifythat it should be displayed as a hyperlink that points to the detailed view of the post

Tip When displaying text we call CHtmlencode() to encode HTML entities init This prevents from cross-site scripting attack

342 Deleting Posts

In the admin data grid there is a delete button in each row Clicking on the button shoulddelete the corresponding post Internally this triggers the delete action implemented asfollows

30 3 Post Management

public function actionDelete()

if(Yiiapp()-gtrequest-gtisPostRequest)

we only allow deletion via POST request

$this-gtloadModel()-gtdelete()

if(isset($ POST[rsquoajaxrsquo]))

$this-gtredirect(array(rsquoindexrsquo))

else

throw new CHttpException(400rsquoInvalid request Please do not repeat this request againrsquo)

The above code is the one generated by the yiic tool without any change We wouldlike to explain a little bit more about the checking on $ POST[rsquoajaxrsquo] The [CGridView]widget has a very nice feature that its sorting pagination and deletion operations are alldone in AJAX mode by default That means the whole page does not get reloaded if anyof the above operations is performed However it is also possible that the widget runs innon-AJAX mode (by setting its ajaxUpdate property to be false or disabling JavaScript onthe client side) It is necessary for the delete action to differentiate these two scenarios ifthe delete request is made via AJAX we should not redirect the user browser otherwisewe should

Deleting a post should also cause the deletion of all comments for that post In additionwe should also update the tbl tag table regarding the tags for the deleted post Both ofthese tasks can be achieved by writing an afterDelete method in the Post model class asfollows

protected function afterDelete()

parentafterDelete()

Commentmodel()-gtdeleteAll(rsquopost id=rsquo$this-gtid)

Tagmodel()-gtupdateFrequency($this-gttags rsquorsquo)

The above code is very straightforward it first deletes all those comments whose post id

is the same as the ID of the deleted post it then updates the tbl tag table for the tags

of the deleted post

34 Managing Posts 31

Tip We have to explicitly delete all comments for the deleted post here becauseSQLite does not really support foreign key constraints In a DBMS that supportsthis constraint (such as MySQL PostgreSQL) the foreign key constraint can be setup such that the DBMS automatically deletes the related comments if the post isdeleted In that case we no longer this explicit deletion call in our code

32 3 Post Management

Chapter 4

Comment Management

41 Customizing Comment Model

For the Comment model we mainly need to customize the rules() and attributeLabels()

methods The attributeLabels() method returns a mapping between attribute namesand attribute labels We do not need to touch relations() since the code generated bythe yiic tool is good enough

411 Customizing rules() Method

We first customize the validation rules generated by the yiic tool The following rules areused for comments

public function rules()

return array(

array(rsquocontent author emailrsquo rsquorequiredrsquo)

array(rsquoauthor email urlrsquo rsquolengthrsquo rsquomaxrsquo=gt128)

array(rsquoemailrsquorsquoemailrsquo)

array(rsquourlrsquorsquourlrsquo)

)

In the above we specify that the author email and content attributes are required thelength of author email and url cannot exceed 128 the email attribute must be a validemail address and the url attribute must be a valid URL

412 Customizing attributeLabels() Method

We then customize the attributeLabels() method to declare the label display for eachmodel attribute This method returns an array consisting of name-label pairs When wecall CHtmlactiveLabel() to display an attribute label

34 4 Comment Management

public function attributeLabels()

return array(

rsquoidrsquo =gt rsquoIdrsquo

rsquocontentrsquo =gt rsquoCommentrsquo

rsquostatusrsquo =gt rsquoStatusrsquo

rsquocreate timersquo =gt rsquoCreate Timersquo

rsquoauthorrsquo =gt rsquoNamersquo

rsquoemailrsquo =gt rsquoEmailrsquo

rsquourlrsquo =gt rsquoWebsitersquo

rsquopost idrsquo =gt rsquoPostrsquo

)

Tip If the label for an attribute is not declared in attributeLabels() an algo-rithm will be used to generate an appropriate label For example a label VerifyCode will be generated for attributes verify code or verifyCode

413 Customizing Saving Process

Because we want to record the creation time of a comment we override the beforeSave()

method of Comment like we do for the Post model

protected function beforeSave()

if(parentbeforeSave())

if($this-gtisNewRecord)

$this-gtcreate time=time()

return true

else

return false

42 Creating and Displaying Comments

In this section we implement the comment display and creation features

421 Displaying Comments

Instead of displaying and creating comments on individual pages we use the post detailpage (generated by the view action of PostController) Below the post content display

42 Creating and Displaying Comments 35

we display first a list of comments belonging to that post and then a comment creationform

In order to display comments on the post detail page we modify the view script wwwrootblogprotectedviewspostviewphp as follows

ltdiv id=commentsgt

ltphp if($model-gtcommentCountgt=1) gt

lth3gt

ltphp echo $model-gtcommentCount rsquocomment(s)rsquo gt

lth3gt

ltphp $this-gtrenderPartial(rsquo commentsrsquoarray(

rsquopostrsquo=gt$model

rsquocommentsrsquo=gt$model-gtcomments

)) gt

ltphp endif gt

ltdivgt

In the above we call renderPartial() to render a partial view named comments to displaythe list of comments belonging to the current post Note that in the view we use theexpression $model-gtcomments to retrieve the comments for the post This is valid becausewe have declared a comments relation in the Post class Evaluating this expression wouldtrigger an implicit JOIN database query to bring back the proper comments This featureis known as lazy relational query

The partial view comments is not very interesting It mainly goes through every commentand displays the detail of it Interested readers may refer to wwwrootyiidemosblog

protectedpost commentsphp

422 Creating Comments

To handle comment creation we first modify the actionView() method of PostController

as follows

public function actionView()

$post=$this-gtloadModel()

$comment=$this-gtnewComment($post)

$this-gtrender(rsquoviewrsquoarray(

rsquomodelrsquo=gt$post

rsquocommentrsquo=gt$comment

))

36 4 Comment Management

protected function newComment($post)

$comment=new Comment

if(isset($ POST[rsquoCommentrsquo]))

$comment-gtattributes=$ POST[rsquoCommentrsquo]

if($post-gtaddComment($comment))

if($comment-gtstatus==CommentSTATUS PENDING)

Yiiapp()-gtuser-gtsetFlash(rsquocommentSubmittedrsquorsquoThank yoursquo)

$this-gtrefresh()

return $comment

In the above we call the newComment() method before we render view In the newComment()

method we generate a Comment instance and check if the comment form is submitted If sowe try to add the comment for the post by calling $post-gtaddComment($comment) If it goesthrough we refresh the post detail page In case the comment needs to be approved we willshow a flash message to indicate this decision A flash message is usually a confirmationmessage displayed to end users If the user clicks on the refresh button of his browser themessage will disappear

We also need to modify wwwrootblogprotectedviewspostviewphp furthermore

ltdiv id=commentsgt

lth3gtLeave a Commentlth3gt

ltphp if(Yiiapp()-gtuser-gthasFlash(rsquocommentSubmittedrsquo)) gt

ltdiv class=successgt

ltphp echo Yiiapp()-gtuser-gtgetFlash(rsquocommentSubmittedrsquo) gt

ltdivgt

ltphp else gt

ltphp $this-gtrenderPartial(rsquocomment formrsquoarray(

rsquomodelrsquo=gt$comment

)) gt

ltphp endif gt

ltdivgtlt-- comments --gt

43 Managing Comments 37

In the above code we display the flash message if it is available If not we displaythe comment input form by rendering the partial view wwwrootblogprotectedviews

comment formphp

43 Managing Comments

Comment management includes updating deleting and approving comments These op-erations are implemented as actions in the CommentController class

431 Updating and Deleting Comments

The code generated by yiic for updating and deleting comments remains largely un-changed

432 Approving Comments

When comments are newly created they are in pending approval status and need to beapproved in order to be visible to guest users Approving a comment is mainly aboutchanging the status column of the comment

We create an actionApprove() method in CommentController as follows

public function actionApprove()

if(Yiiapp()-gtrequest-gtisPostRequest)

$comment=$this-gtloadModel()

$comment-gtapprove()

$this-gtredirect(array(rsquoindexrsquo))

else

throw new CHttpException(400rsquoInvalid requestrsquo)

In the above when the approve action is invoked via a POST request we call the approve()

method defined in the Comment model to change the status We then redirect the userbrowser to the page displaying the post that this comment belongs to

We also modify the actionIndex() method of Comment to show all comments We wouldlike to see comments pending approved to show up first

public function actionIndex()

38 4 Comment Management

$dataProvider=new CActiveDataProvider(rsquoCommentrsquo array(

rsquocriteriarsquo=gtarray(

rsquowithrsquo=gtrsquopostrsquo

rsquoorderrsquo=gtrsquotstatus tcreate time DESCrsquo

)

))

$this-gtrender(rsquoindexrsquoarray(

rsquodataProviderrsquo=gt$dataProvider

))

Notice that in the above code because both tbl post and tbl comment have columns statusand create time we need to disambiguate the corresponding column reference by prefixingthem with table alias names As described in the guide the alias for the primary table ina relational query is always t Therefore we are prefixing t to the status and create time

columns in the above code

Like the post index view the index view for CommentController uses [CListView] to displaythe comment list which in turn uses the partial view wwwrootblogprotectedviews

comment viewphp to display the detail of each individual comment We will not go intodetails here Interested readers may refer to the corresponding file in the blog demowwwrootyiidemosblogprotectedviewscomment viewphp

Chapter 5

Portlets

51 Customizing Page Layout

Features like rdquothe most recent commentsrdquo rdquotag cloudrdquo are better to be implemented inportlets A portlet is a pluggable user interface component that renders a fragment ofHTML code In this section we describe how to set up the portlet architecture for ourblog application

Before we go into details about developing portlets we first need to adjust our page layoutso that the portlets can be placed in a side bar

A page layout determines how to share common sections (eg header footer) amongdifferent pages and how to embed contents that are specific for different pages In Yiia page layout is represented as a view under the protectedviewslayouts folder Whenwe call the render() method in a controller the rendering result will be automaticallyembedded into this layout view before it is displayed to end users The default layoutview is named as main which means the wwwrootblogprotectedviewslayoutsmain

php file in our blog application

We could directly modify the main layout to add a side bar However doing so wouldmake some irrelevant pages such as about contact pages to have a side bar which is notalways desirable For this reason we need different layouts for different pages

We thus create two different layouts column1 and column2 The former displays the pagein a single column while the latter displays two columns in the page one of which is theside bar We will use column2 layout for the post and comment pages and column1 forthe rest of the pages Because both column1 and column2 still share the same header andfooter sections we let them to share the main layout

Based on the above description we create the column1 layout as follows

ltphp $this-gtbeginContent(rsquolayoutsmainrsquo) gt

ltdiv class=containergt

40 5 Portlets

ltdiv id=contentgt

ltphp echo $content gt

ltdivgtlt-- content --gt

ltdivgt

ltphp $this-gtendContent() gt

and the column2 layout as follows

ltphp $this-gtbeginContent(rsquolayoutsmainrsquo) gt

ltdiv class=containergt

ltdiv class=span-18gt

ltdiv id=contentgt

ltphp echo $content gt

ltdivgtlt-- content --gt

ltdivgt

ltdiv class=span-6 lastgt

ltdiv id=sidebargt

portlets to be placed here

ltdivgtlt-- sidebar --gt

ltdivgt

ltdivgt

ltphp $this-gtendContent() gt

As we can see both column1 and column2 are enclosed in a pair of beginContent andendContent calls They essentially use the specified view layoutsmain to decorate theenclosed content In all these layout views the variable $content refers to the dynamiccontent to be embedded into the layout

Besides customizing the layout view file we also need to adjust the CSS file wwwroot

blogcssmaincss so that the overall appearance would look like what we see in the blogdemo We will not go into details here

Info The skeleton application generated by the yiic tool uses the BluePrint CSSframework It provides a nice font reset and a robust grid system For more detailson how to use this framework please refer to its documentation

After creating the new layouts we need to set the layout property of controllers to makeuse of them So far we have three controllers SiteController PostController andCommentController We set SiteController to use column1 as the default layout whilethe rest two of the controllers use column2 for example

class PostController extends Controller

52 Creating User Menu Portlet 41

public $layout=rsquocolumn2rsquo

Tip If some view of the controller does not use the same layout as declaredby the layout property it may customize it in the view by a simple assignment$this-gtlayout=rsquonewlayoutrsquo

52 Creating User Menu Portlet

Based on the requirements analysis we need three different portlets the rdquouser menurdquoportlet the rdquotag cloudrdquo portlet and the rdquorecent commentsrdquo portlet We will develop theseportlets by extending the [CPortlet] widget provided by Yii

In this section we will develop our first concrete portlet - the user menu portlet whichdisplays a list of menu items that are only available to authenticated users The menucontains four items

bull Approve Comments a hyperlink that leads to a list of comments pending approval

bull Create New Post a hyperlink that leads to the post creation page

bull Manage Posts a hyperlink that leads to the post management page

bull Logout a link button that would log out the current user

521 Creating UserMenu Class

We create the UserMenu class to represent the logic part of the user menu portlet Theclass is saved in the file wwwrootblogprotectedcomponentsUserMenuphp which has thefollowing content

Yiiimport(rsquoziiwidgetsCPortletrsquo)

class UserMenu extends CPortlet

public function init()

$this-gttitle=CHtmlencode(Yiiapp()-gtuser-gtname)

parentinit()

42 5 Portlets

protected function renderContent()

$this-gtrender(rsquouserMenursquo)

The UserMenu class extends from the CPortlet class that we created previously It overridesboth the init() method and the renderContent() method of CPortlet The former setsthe portlet title to be the name of the current user the latter generates the portlet bodycontent by rendering a view named userMenu

Tip Notice that we have to explicitly include the CPortlet class by calling Yii

import() before we refer to it the first time This is because CPortlet is part of thezii project ndash the official extension library for Yii For performance considerationclasses in this project are not listed as core classes Therefore we have to import itbefore we use it the first time

522 Creating userMenu View

Next we create the userMenu view which is saved in the file wwwrootblogprotected

componentsviewsuserMenuphp

ltulgt

ltligtltphp echo CHtmllink(rsquoCreate New Postrsquoarray(rsquopostcreatersquo)) gtltligt

ltligtltphp echo CHtmllink(rsquoManage Postsrsquoarray(rsquopostadminrsquo)) gtltligt

ltligtltphp echo CHtmllink(rsquoApprove Commentsrsquoarray(rsquocommentindexrsquo))

rsquo (rsquo Commentmodel()-gtpendingCommentCount rsquo)rsquo gtltligt

ltligtltphp echo CHtmllink(rsquoLogoutrsquoarray(rsquositelogoutrsquo)) gtltligt

ltulgt

Info By default view files for a widget should be placed under the views sub-directory of the directory containing the widget class file The file name must bethe same as the view name

523 Using UserMenu Portlet

It is time for us to make use of our newly completed UserMenu portlet We modify thelayout view file wwwrootblogprotectedviewslayoutscolumn2php as follows

53 Creating Tag Cloud Portlet 43

ltdiv id=sidebargt

ltphp if(Yiiapp()-gtuser-gtisGuest) $this-gtwidget(rsquoUserMenursquo) gt

ltdivgt

In the above we call the widget() method to generate and execute an instance of theUserMenu class Because the portlet should only be displayed to authenticated users weonly call widget() when the isGuest property of the current user is false (meaning theuser is authenticated)

524 Testing UserMenu Portlet

Letrsquos test what we have so far

1 Open a browser window and enter the URL httpwwwexamplecomblogindex

php Verify that there is nothing displayed in the side bar section of the page

2 Click on the Login hyperlink and fill out the login form to login If successful verifythat the UserMenu portlet appears in the side bar and the portlet has the usernameas its title

3 Click on the rsquoLogoutrsquo hyperlink in the UserMenu portlet Verify that the logout actionis successful and the UserMenu portlet disappears

525 Summary

What we have created is a portlet that is highly reusable We can easily reuse it in adifferent project with little or no modification Moreover the design of this portlet followsclosely the philosophy that logic and presentation should be separated While we did notpoint this out in the previous sections such practice is used nearly everywhere in a typicalYii application

53 Creating Tag Cloud Portlet

Tag cloud displays a list of post tags with visual decorations hinting the popularity of eachindividual tag

531 Creating TagCloud Class

We create the TagCloud class in the file wwwrootblogprotectedcomponentsTagCloud

php The file has the following content

44 5 Portlets

Yiiimport(rsquoziiwidgetsCPortletrsquo)

class TagCloud extends CPortlet

public $title=rsquoTagsrsquo

public $maxTags=20

protected function renderContent()

$tags=Tagmodel()-gtfindTagWeights($this-gtmaxTags)

foreach($tags as $tag=gt$weight)

$link=CHtmllink(CHtmlencode($tag) array(rsquopostindexrsquorsquotagrsquo=gt$tag))

echo CHtmltag(rsquospanrsquo array(

rsquoclassrsquo=gtrsquotagrsquo

rsquostylersquo=gtfont-size$weightpt) $link)n

Unlike the UserMenu portlet the TagCloud portlet does not use a view Instead its presen-tation is done in the renderContent() method This is because the presentation does notcontain much HTML tags

We display each tag as a hyperlink to the post index page with the corresponding tagparameter The font size of each tag link is adjusted according to their relative weightamong other tags If a tag has higher frequency value than the other it will have a biggerfont size

532 Using TagCloud Portlet

Usage of the TagCloud portlet is very simple We modify the layout file wwwrootblog

protectedviewslayoutsmainphp as follows

ltdiv id=sidebargt

ltphp if(Yiiapp()-gtuser-gtisGuest) $this-gtwidget(rsquoUserMenursquo) gt

ltphp $this-gtwidget(rsquoTagCloudrsquo array(

rsquomaxTagsrsquo=gtYiiapp()-gtparams[rsquotagCloudCountrsquo]

)) gt

ltdivgt

54 Creating Recent Comments Portlet 45

54 Creating Recent Comments Portlet

In this section we create the last portlet that displays a list of comments recently pub-lished

541 Creating RecentComments Class

We create the RecentComments class in the file wwwrootblogprotectedcomponentsRecentCommentsphp The file has the following content

Yiiimport(rsquoziiwidgetsCPortletrsquo)

class RecentComments extends CPortlet

public $title=rsquoRecent Commentsrsquo

public $maxComments=10

public function getRecentComments()

return Commentmodel()-gtfindRecentComments($this-gtmaxComments)

protected function renderContent()

$this-gtrender(rsquorecentCommentsrsquo)

In the above we invoke the findRecentComments method which is defined in the Comment

class as follows

class Comment extends CActiveRecord

public function findRecentComments($limit=10)

return $this-gtwith(rsquopostrsquo)-gtfindAll(array(

rsquoconditionrsquo=gtrsquotstatus=rsquoselfSTATUS APPROVED

rsquoorderrsquo=gtrsquotcreate time DESCrsquo

rsquolimitrsquo=gt$limit

))

46 5 Portlets

542 Creating recentComments View

The recentComments view is saved in the file wwwrootblogprotectedcomponentsviews

recentCommentsphp It simply displays every comment returned by the RecentComments

getRecentComments() method

543 Using RecentComments Portlet

We modify the layout file wwwrootblogprotectedviewslayoutsmainphp to embedthis last portlet

ltdiv id=sidebargt

ltphp if(Yiiapp()-gtuser-gtisGuest) $this-gtwidget(rsquoUserMenursquo) gt

ltphp $this-gtwidget(rsquoTagCloudrsquo array(

rsquomaxTagsrsquo=gtYiiapp()-gtparams[rsquotagCloudCountrsquo]

)) gt

ltphp $this-gtwidget(rsquoRecentCommentsrsquo array(

rsquomaxCommentsrsquo=gtYiiapp()-gtparams[rsquorecentCommentCountrsquo]

)) gt

ltdivgt

Chapter 6

Final Work

61 Beautifying URLs

The URLs linking various pages of our blog application currently look ugly For examplethe URL for the page showing a post looks like the following

indexphpr=postshowampid=1

In this section we describe how to beautify these URLs and make them SEO-friendlyOur goal is to be able to use the following URLs in the application

1 indexphppostsyii leads to the page showing a list of posts with tag yii

2 indexphppost2A+Test+Post leads to the page showing the detail of the postwith ID 2 whose title is A Test Post

3 indexphppostupdate1 leads to the page that allows updating the post with ID1

Note that in the second URL format we include the post title in the URL This is mainlyto make the URL SEO friendly It is said that search engines may also respect the wordsfound in a URL when it is being indexed

To achieve our goal we modify the application configuration as follows

return array(

rsquocomponentsrsquo=gtarray(

rsquourlManagerrsquo=gtarray(

rsquourlFormatrsquo=gtrsquopathrsquo

rsquorulesrsquo=gtarray(

48 6 Final Work

rsquopostltidd+gtlttitlegtrsquo=gtrsquopostviewrsquorsquopostslttaggtrsquo=gtrsquopostindexrsquo

)

)

)

)

In the above we configure the urlManager component by setting its urlFormat propertyto be path and adding a set of rules

The rules are used by urlManager to parse and create the URLs in the desired formatFor example the first rule says that if a URL indexphppostsyii is requested theurlManager component should be responsible to dispatch the request to the route post

index and generate a tag GET parameter with the value yii On the other hand whencreating a URL with the route postindex and parameter tag the urlManager componentwill also use this rule to generate the desired URL indexphppostsyii For this reasonwe say that urlManager is a two-way URL manager

The urlManager component can further beautify our URLs such as hiding indexphp inthe URLs appending suffix like html to the URLs We can obtain these features easilyby configuring various properties of urlManager in the application configuration For moredetails please refer to the Guide

62 Logging Errors

A production Web application often needs sophisticated logging for various events In ourblog application we would like to log the errors occurring when it is being used Sucherrors could be programming mistakes or usersrsquo misuse of the system Logging these errorswill help us to improve the blog application

We enable the error logging by modifying the application configuration as follows

return array(

rsquopreloadrsquo=gtarray(rsquologrsquo)

rsquocomponentsrsquo=gtarray(

rsquologrsquo=gtarray(

rsquoclassrsquo=gtrsquoCLogRouterrsquo

rsquoroutesrsquo=gtarray(

array(

rsquoclassrsquo=gtrsquoCFileLogRoutersquo

rsquolevelsrsquo=gtrsquoerror warningrsquo

63 Final Tune-up and Deployment 49

)

)

)

)

)

With the above configuration if an error or warning occurs detailed information will belogged and saved in a file located under the directory wwwrootblogprotectedruntime

The log component offers more advanced features such as sending log messages to a listof email addresses displaying log messages in JavaScript console window etc For moredetails please refer to the Guide

63 Final Tune-up and Deployment

We are close to finish our blog application Before deployment we would like to do sometune-ups

631 Changing Home Page

We change to use the post list page as the home page We modify the application config-uration as follows

return array(

rsquodefaultControllerrsquo=gtrsquopostrsquo

)

Tip Because PostController already declares list to be its default action whenwe access the home page of the application we will see the result generated by thelist action of the post controller

632 Enabling Schema Caching

Because ActiveRecord relies on the metadata about tables to determine the column infor-mation it takes time to read the metadata and analyze it This may not be a problemduring development stage but for an application running in production mode it is a totalwaste of time if the database schema does not change Therefore we should enable theschema caching by modifying the application configuration as follows

50 6 Final Work

return array(

rsquocomponentsrsquo=gtarray(

rsquocachersquo=gtarray(

rsquoclassrsquo=gtrsquoCDbCachersquo

)

rsquodbrsquo=gtarray(

rsquoclassrsquo=gtrsquosystemdbCDbConnectionrsquo

rsquoconnectionStringrsquo=gtrsquosqlitewwwrootblogprotecteddatablogdbrsquo

rsquoschemaCachingDurationrsquo=gt3600

)

)

)

In the above we first add a cache component which uses a default SQLite database as thecaching storage If our server is equipped with other caching extensions such as APCwe could change to use them as well We also modify the db component by setting itsschemaCachingDuration property to be 3600 which means the parsed database schemadata can remain valid in cache for 3600 seconds

633 Disabling Debugging Mode

We modify the entry script file wwwrootblogindexphp by removing the line definingthe constant YII DEBUG This constant is useful during development stage because it allowsYii to display more debugging information when an error occurs However when theapplication is running in production mode displaying debugging information is not agood idea because it may contain sensitive information such as where the script file islocated and the content in the file etc

634 Deploying the Application

The final deployment process manly involves copying the directory wwwrootblog to thetarget directory The following checklist shows every needed step

1 Install Yii in the target place if it is not available

2 Copy the entire directory wwwrootblog to the target place

3 Edit the entry script file indexphp by pointing the $yii variable to the new Yiibootstrap file

4 Edit the file protectedyiicphp by setting the $yiic variable to be the new Yiiyiicphp file

64 Future Enhancements 51

5 Change the permission of the directories assets and protectedruntime so that theyare writable by the Web server process

64 Future Enhancements

641 Using a Theme

Without writing any code our blog application is already themeable To use a themewe mainly need to develop the theme by writing customized view files in the theme Forexample to use a theme named classic that uses a different page layout we would createa layout view file wwwrootblogthemesclassicviewslayoutsmainphp We also needto change the application configuration to indicate our choice of the classic theme

return array(

rsquothemersquo=gtrsquoclassicrsquo

)

642 Internationalization

We may also internationalize our blog application so that its pages can be displayed indifferent languages This mainly involves efforts in two aspects

First we may create view files in different languages For example for the list pageof PostController we can create a view file wwwrootblogprotectedviewspostzh cn

listphp When the application is configured to use simplified Chinese (the language codeis zh cn) Yii will automatically use this new view file instead of the original one

Second we may create message translations for those messages generated by code Themessage translations should be saved as files under the directory wwwrootblogprotected

messages We also need to modify the code where we use text strings by enclosing themin the method call Yiit()

For more details about internationalization please refer to the Guide

643 Improving Performance with Cache

While the Yii framework itself is very efficient it is not necessarily true that an applicationwritten in Yii efficient There are several places in our blog application that we can improvethe performance For example the tag clould portlet could be one of the performancebottlenecks because it involves complex database query and PHP logic

52 6 Final Work

We can make use of the sophisticated caching feature provided by Yii to improve theperformance One of the most useful components in Yii is COutputCache which caches afragment of page display so that the underlying code generating the fragment does not needto be executed for every request For example in the layout file wwwrootblogprotected

viewslayoutsmainphp we can enclose the tag cloud portlet with COutputCache

ltphp if($this-gtbeginCache(rsquotagCloudrsquo array(rsquodurationrsquo=gt3600))) gt

ltphp $this-gtwidget(rsquoTagCloudrsquo array(

rsquomaxTagsrsquo=gtYiiapp()-gtparams[rsquotagCloudCountrsquo]

)) gt

ltphp $this-gtendCache() gt

With the above code the tag cloud display will be served from cache instead of beinggenerated on-the-fly for every request The cached content will remain valid in cache for3600 seconds

644 Adding New Features

Our blog application only has very basic functionalities To become a complete blogsystem more features are needed for example calendar portlet email notifications postcategorization archived post portlet and so on We will leave the implementation of thesefeatures to interested readers

  • Contents
  • License
  • Getting Started
    • Building a Blog System using Yii
    • Testdriving with Yii
      • Installing Yii
      • Creating Skeleton Application
      • Application Workflow
        • Requirements Analysis
        • Overall Design
          • Initial Prototyping
            • Setting Up Database
              • Creating Database
              • Establishing Database Connection
                • Scaffolding
                • Authenticating User
                • Summary
                  • Post Management
                    • Customizing Post Model
                      • Customizing rules() Method
                      • Customizing relations() Method
                      • Representing Status in Text
                        • Creating and Updating Posts
                          • Customizing Access Control
                          • Customizing create and update Operations
                            • Displaying Posts
                              • Customizing view Operation
                              • Customizing index Operation
                                • Managing Posts
                                  • Listing Posts in Tabular View
                                  • Deleting Posts
                                      • Comment Management
                                        • Customizing Comment Model
                                          • Customizing rules() Method
                                          • Customizing attributeLabels() Method
                                          • Customizing Saving Process
                                            • Creating and Displaying Comments
                                              • Displaying Comments
                                              • Creating Comments
                                                • Managing Comments
                                                  • Updating and Deleting Comments
                                                  • Approving Comments
                                                      • Portlets
                                                        • Customizing Page Layout
                                                        • Creating User Menu Portlet
                                                          • Creating UserMenu Class
                                                          • Creating userMenu View
                                                          • Using UserMenu Portlet
                                                          • Testing UserMenu Portlet
                                                          • Summary
                                                            • Creating Tag Cloud Portlet
                                                              • Creating TagCloud Class
                                                              • Using TagCloud Portlet
                                                                • Creating Recent Comments Portlet
                                                                  • Creating RecentComments Class
                                                                  • Creating recentComments View
                                                                  • Using RecentComments Portlet
                                                                      • Final Work
                                                                        • Beautifying URLs
                                                                        • Logging Errors
                                                                        • Final Tune-up and Deployment
                                                                          • Changing Home Page
                                                                          • Enabling Schema Caching
                                                                          • Disabling Debugging Mode
                                                                          • Deploying the Application
                                                                            • Future Enhancements
                                                                              • Using a Theme
                                                                              • Internationalization
                                                                              • Improving Performance with Cache
                                                                              • Adding New Features
Page 12: Yii Blog 1.1.0 Demo Tutor

14 Overall Design 5

14 Overall Design

Based on the analysis of the requirements we decide to use the following database tablesto store the persistent data for our blog application

bull tbl user stores the user information including username and password

bull tbl post stores the blog post information It mainly consists of the following columns

ndash title required title of the post

ndash content required body content of the post which uses the Markdown format

ndash status required status of the post which can be one of following values

lowast 1 meaning the post is in draft and is not visible to public

lowast 2 meaning the post is published to public

lowast 3 meaning the post is outdated and is not visible in the post list (stillaccessible individually though)

ndash tags optional a list of comma-separated words categorizing the post

bull tbl comment stores the post comment information Each comment is associated witha post and mainly consists of the following columns

ndash name required the author name

ndash email required the author email

ndash website optional the author website URL

ndash content required the comment content in plain text format

ndash status required status of the comment which indicates whether the commentis approved (value 2) or not (value 1)

bull tbl tag stores post tag frequency information that is needed to implement the tagcloud feature The table mainly contains the following columns

ndash name required the unique tag name

ndash frequency required the number of times that the tag appears in posts

bull tbl lookup stores generic lookup information It is essentially a map between integervalues and text strings The former is the data representation in our code while thelatter is the corresponding presentation to end users For example we use integer1 to represent the draft post status and string Draft to display this status to endusers This table mainly contains the following columns

6 1 Getting Started

ndash name the textual representation of the data item that is to be displayed to endusers

ndash code the integer representation of the data item

ndash type the type of the data item

ndash position the relative display order of the data item among other items of thesame type

The following entity-relation (ER) diagram shows the table structure and relationshipsabout the above tables

Figure 11 Entity-Relation Diagram of the Blog Database

Complete SQL statements corresponding to the above ER diagram may be found inthe blog demo In our Yii installation they are in the file wwwrootyiidemosblog

protecteddataschemasqlitesql

Info We name all our table names and column names in lower case This is becausedifferent DBMS often have different case-sensitivity treatment and we want to avoidtroubles like this

We also prefix all our tables with tbl This serves for two purposes First the prefixintroduces a namespace to these tables in case when they need to coexist with othertables in the same database which often happens in a shared hosting environmentwhere a single database is being used by multiple applications Second using tableprefix reduces the possibility of having some table names that are reserved keywordsin DBMS

14 Overall Design 7

We divide the development of our blog application into the following milestones

bull Milestone 1 creating a prototype of the blog system It should consist of most ofthe required functionalities

bull Milestone 2 completing post management It includes creating listing showingupdating and deleting posts

bull Milestone 3 completing comment management It includes creating listing ap-proving updating and deleting post comments

bull Milestone 4 implementing portlets It includes user menu login tag cloud andrecent comments portlets

bull Milestone 5 final tune-up and deployment

8 1 Getting Started

Chapter 2

Initial Prototyping

21 Setting Up Database

Having created a skeleton application and finished the database design in this section wewill create the blog database and establish the connection to it in the skeleton application

211 Creating Database

We choose to create a SQLite database Because the database support in Yii is built on topof PDO we can easily switch to use a different type of DBMS (eg MySQL PostgreSQL)without the need to change our application code

We create the database file blogdb under the directory wwwrootblogprotecteddataNote that both the directory and the database file have to be writable by the Web serverprocess as required by SQLite We may simply copy the database file from the blogdemo in our Yii installation which is located at wwwrootyiidemosblogprotecteddatablogdb We may also generate the database by executing the SQL statements in the filewwwrootyiidemosblogprotecteddataschemasqlitesql

Tip To execute SQL statements we may use the sqlite3 command line tool thatcan be found in the SQLite official website

212 Establishing Database Connection

To use the blog database in the skeleton application we created we need to modify itsapplication configuration which is stored in the PHP script wwwrootblogprotected

configmainphp The script returns an associative array consisting of name-value pairseach of which is used to initialize a writable property of the application instance

We configure the db component as follows

10 2 Initial Prototyping

return array(

rsquocomponentsrsquo=gtarray(

rsquodbrsquo=gtarray(

rsquoconnectionStringrsquo=gtrsquosqlitewwwrootblogprotecteddatablogdbrsquo

rsquotablePrefixrsquo=gtrsquotbl rsquo

)

)

)

The above configuration says that we have a db application component whose connectionStringproperty should be initialized as sqlitewwwrootblogprotecteddatablogdb and whosetablePrefix property should be tbl

With this configuration we can access the DB connection object using Yiiapp()-gtdb

at any place in our code Note that Yiiapp() returns the application instance that wecreate in the entry script If you are interested in possible methods and properties that theDB connection has you may refer to its class reference However in most cases we are notgoing to use this DB connection directly Instead we will use the so-called ActiveRecordto access the database

We would like to explain a bit more about the tablePrefix property that we set in theconfiguration This tells the db connection that it should respect the fact we are usingtbl as the prefix to our database table names In particular if in a SQL statement thereis a token enclosed within double curly brackets (eg post then the db connectionshould translate it into a name with the table prefix (eg tbl post) before sending it toDBMS for execution This feature is especially useful if in future we need to modify thetable name prefix without touching our source code For example if we are developing ageneric content management system (CMS) we may exploit this feature so that when itis being installed in a new environment we can allow users to choose a table prefix theylike

22 Scaffolding 11

Tip If you want to use MySQL instead of SQLite to store data you may cre-ate a MySQL database named blog using the SQL statements in wwwrootyii

demosblogprotecteddataschemamysqlsql Then modify the applicationconfiguration as follows

return array(

rsquocomponentsrsquo=gtarray(

rsquodbrsquo=gtarray(

rsquoconnectionStringrsquo =gt rsquomysqlhost=localhostdbname=blogrsquo

rsquoemulatePreparersquo =gt true

rsquousernamersquo =gt rsquorootrsquo

rsquopasswordrsquo =gt rsquorsquo

rsquocharsetrsquo =gt rsquoutf8rsquo

rsquotablePrefixrsquo =gt rsquotbl rsquo

)

)

)

22 Scaffolding

Create read update and delete (CRUD) are the four basic operations of data objects in anapplication Because the task of implementing the CRUD operations is so common whendeveloping Web applications Yii provides a tool to automate this process (also known asscaffolding) In this section we will describe how to use the tool to implement CRUD forposts and comments

Open a command window and run the following commands

wwwrootyiiframeworkyiic shell wwwrootblogindexphp

Yii Interactive Tool v11

Please type rsquohelprsquo for help Type rsquoexitrsquo to quit

gtgt model

gtgt crud Post

gtgt crud Comment

gtgt exit

12 2 Initial Prototyping

Info Some PHP installations may use a different phpini file for command line(CLI) PHP parser As a result when running the above yiic commands youmay encounter errors like rdquoYiiBaseinclude(PDOphp) failed to open streamrdquoor rdquocould not find driverrdquo Please double check your CLI PHP configuration byexecuting the following command

php -r phpinfo()

The result of the above command will show which phpini file is being used andwhich extensions are loaded If a wrong phpini file is used you may use thefollowing command to explicitly specify the correct phpini to use

php -c phpini wwwrootyiiframeworkyiicphp shell wwwrootblogindexphp

The commands above accomplish two tasks First the model command generates a modelclass file for each table in the blog database Second the crud commands generate thecode needed by the CRUD operations for the Post and Comment models

Tip The command model generates a model class for every table in the databaseSometimes we probably donrsquot want to do so (eg the database contains some irrel-evant tables) In this case we can create model classes one by one For exampleto create the User model we can use the command model User The model com-mand also has some more advanced usages For more details execute help model

command

We can test the generated code by accessing the following URLs

httpwwwexamplecomblogindexphpr=post

httpwwwexamplecomblogindexphpr=comment

Notice that the post and comment features implemented by the generated code are com-pletely independent of each other Also when creating a new post or comment we arerequired to enter information such as author id and create time which in real applica-tion should be set by the program Donrsquot worry We will fix these problems in the nextmilestones For now we should be fairly satisfied as this prototype already contains mostfeatures that we need to implement for the blog application

To prepare for the next milestones letrsquos take a closer look at the files generated by theabove commands All the files are generated under wwwrootblogprotected For conve-nience we group them into model files controller files and view files

22 Scaffolding 13

bull model files

ndash modelsUserphp contains the User class that extends from CActiveRecord andcan be used to access the tbl user database table

ndash modelsPostphp contains the Post class that extends from CActiveRecord andcan be used to access the tbl post database table

ndash modelsTagphp contains the Tag class that extends from CActiveRecord andcan be used to access the tbl tag database table

ndash modelsCommentphp contains the Comment class that extends from CActiveRe-cord and can be used to access the tbl comment database table

ndash modelsLookupphp contains the Lookup class that extends from CActiveRecordand can be used to access the tbl lookup database table

bull controller file

ndash controllersPostControllerphp contains the PostController class which is thecontroller in charge of all CRUD operations about posts

ndash controllersCommentControllerphp contains the CommentController class whichis the controller in charge of all CRUD operations about comments

bull view files

ndash viewspostcreatephp is the view file that shows an HTML form to create anew post

ndash viewspostupdatephp is the view file that shows an HTML form to update anexisting post

ndash viewspostviewphp is the view file that displays the detailed information of apost

ndash viewspostindexphp is the view file that displays a list of posts

ndash viewspostadminphp is the view file that displays posts in a table with ad-ministrative commands

ndash viewspost formphp is the partial view file embedded in viewspostcreate

php and viewspostupdatephp It displays the HTML form for collecting postinformation

ndash viewspost viewphp is the partial view file used by viewspostindexphp Itdisplays the brief view of a single post

ndash a similar set of view files are also generated for comment

14 2 Initial Prototyping

In order to understand better how the above files are used we show in the following theworkflow that occurs in the blog application when displaying a list of posts

1 The user requests the URL httpwwwexamplecomblogindexphpr=posts

2 The entry script is executed by the Web server which creates and initializes anapplication instance to handle the request

3 The application creates an instance of PostController and executes it

4 The PostController instance executes the index action by calling its actionIndex()

method Note that index is the default action if the user does not specify an actionto execute in the URL

5 The actionIndex() method queries database to bring back the list of recent posts

6 The actionIndex() method renders the index view with the post data

23 Authenticating User

Our blog application needs to differentiate between the system owner and guest usersTherefore we need to implement the user authentication feature

As you may have found that the skeleton application already provides user authenticationby checking if the username and password are both demo or admin In this section wewill modify the corresponding code so that the authentication is done against the User

database table

User authentication is performed in a class implementing the IUserIdentity interface Theskeleton application uses the UserIdentity class for this purpose The class is stored inthe file wwwrootblogprotectedcomponentsUserIdentityphp

Tip By convention the name of a class file must be the same as the correspondingclass name suffixed with the extension php Following this convention one canrefer to a class using a path alias For example we can refer to the UserIdentity

class with the alias applicationcomponentsUserIdentity Many APIs in Yiican recognize path aliases (eg YiicreateComponent()) and using path aliasesavoids the necessity of embedding absolute file paths in the code The existence ofthe latter often causes trouble when we deploy an application

We modify the UserIdentity class as follows

23 Authenticating User 15

ltphp

class UserIdentity extends CUserIdentity

private $ id

public function authenticate()

$username=strtolower($this-gtusername)

$user=Usermodel()-gtfind(rsquoLOWER(username)=rsquoarray($username))

if($user===null)

$this-gterrorCode=selfERROR USERNAME INVALID

else if($user-gtvalidatePassword($this-gtpassword))

$this-gterrorCode=selfERROR PASSWORD INVALID

else

$this-gt id=$user-gtid

$this-gtusername=$user-gtusername

$this-gterrorCode=selfERROR NONE

return $this-gterrorCode==selfERROR NONE

public function getId()

return $this-gt id

In the authenticate() method we use the User class to look for a row in the User tablewhose username column is the same as the given username in a case-insensitive mannerRemember that the User class was created using the yiic tool in the prior section Becausethe User class extends from CActiveRecord we can exploit the ActiveRecord feature toaccess the User table in an OOP fashion

In order to check if the user has entered a valid password we invoke the validatePassword

method of the User class We need to modify the file wwwrootblogprotectedmodels

Userphp as follows Note that instead of storing the plain password in the database westore the hash result of the password and a randomly generated salt key When validatingthe user-entered password we should compare the hash results instead

class User extends CActiveRecord

public function validatePassword($password)

return $this-gthashPassword($password$this-gtsalt)===$this-gtpassword

16 2 Initial Prototyping

public function hashPassword($password$salt)

return md5($salt$password)

In the UserIdentity class we also override the getId() method which returns the id valueof the user found in the User table The parent implementation would return the usernameinstead Both the username and id properties will be stored in the user session and maybe accessed via Yiiapp()-gtuser from anywhere in our code

Tip In the UserIdentity class we reference the class CUserIdentity without ex-plicitly including the corresponding class file This is because CUserIdentity is acore class provided by the Yii framework Yii will automatically include the classfile for any core class when it is referenced for the first time

We also do the same with the User class This is because the User class file is placedunder the directory wwwrootblogprotectedmodels which has been added tothe PHP include path according to the following lines found in the applicationconfiguration

return array(

rsquoimportrsquo=gtarray(

rsquoapplicationmodelsrsquo

rsquoapplicationcomponentsrsquo

)

)

The above configuration says that any class whose class file is located under either wwwrootblogprotectedmodels or wwwrootblogprotectedcomponents willbe automatically included when the class is referenced for the first time

The UserIdentity class is mainly used by the LoginForm class to authenticate a user basedon the username and password input collected from the login page The following codefragment shows how UserIdentity is used

$identity=new UserIdentity($username$password)

$identity-gtauthenticate()

switch($identity-gterrorCode)

24 Summary 17

case UserIdentityERROR NONE

Yiiapp()-gtuser-gtlogin($identity)

break

Info People often get confused about identity and the user application componentThe former represents a way of performing authentication while the latter is usedto represent the information related with the current user An application can onlyhave one user component but it can have one or several identity classes dependingon what kind of authentication it supports Once authenticated an identity instancemay pass its state information to the user component so that they are globallyaccessible via user

To test the modified UserIdentity class we can browse the URL httpwwwexample

comblogindexphp and try logging in with the username and password that we store inthe User table If we use the database provided by the blog demo we should be able tologin with username demo and password demo Note that this blog system does not providethe user management feature As a result a user cannot change his account or create anew one through the Web interface The user management feature may be considered asa future enhancement to the blog application

24 Summary

We have completed the milestone 1 Letrsquos summarize what we have done so far

1 We identified the requirements to be fulfilled

2 We installed the Yii framework

3 We created a skeleton application

4 We designed and created the blog database

5 We modified the application configuration by adding the database connection

6 We generated the code that implements the basic CRUD operations for both postsand comments

7 We modified the authentication method to check against the tbl user table

18 2 Initial Prototyping

For a new project most of the time will be spent in step 1 and 4 for this first milestone

Although the code generated by the yiic tool implements fully functional CRUD oper-ations for a database table it often needs to be modified in practical applications Forthis reason in the next two milestone our job is to customize the generated CRUD codeabout posts and comments so that it reaches our initial requirements

In general we first modify the model class file by adding appropriate validation rules anddeclaring relational objects We then modify the controller action and view code for eachindividual CRUD operation

Chapter 3

Post Management

31 Customizing Post Model

The Post model class generated by the yiic tool mainly needs to be modified in two places

bull the rules() method specifies the validation rules for the model attributes

bull the relations() method specifies the related objects

Info A model consists of a list of attributes each associated with a column in thecorresponding database table Attributes can be declared explicitly as class membervariables or implicitly without any declaration

311 Customizing rules() Method

We first specify the validation rules which ensure the attribute values entered by usersare correct before they are saved to the database For example the status attribute ofPost should be an integer 1 2 or 3 The yiic tool also generates validation rules for eachmodel However these rules are based on the table column information and may not beappropriate

Based on the requirement analysis we modify the rules() method as follows

public function rules()

return array(

array(rsquotitle content statusrsquo rsquorequiredrsquo)

array(rsquotitlersquo rsquolengthrsquo rsquomaxrsquo=gt128)

array(rsquostatusrsquo rsquoinrsquo rsquorangersquo=gtarray(123))

array(rsquotagsrsquo rsquomatchrsquo rsquopatternrsquo=gtrsquo^[ws]+$rsquorsquomessagersquo=gtrsquoTags can only contain word charactersrsquo)

20 3 Post Management

array(rsquotagsrsquo rsquonormalizeTagsrsquo)

)

In the above we specify that the title content and status attributes are required thelength of title should not exceed 128 the status attribute value should be 1 (draft) 2(published) or 3 (archived) and the tags attribute should only contain word charactersand commas In addition we use normalizeTags to normalize the user-entered tags so thatthe tags are unique and properly separated with commas

The validators such as required length in and match are all built-in validators providedby Yii The normalizeTags validator is a method-based validator that we need to definein the Post class For more information about how to specify validation rules please referto the Guide

public function normalizeTags($attribute$params)

$this-gttags=Tagarray2string(array unique(Tagstring2array($this-gttags)))

The rules declared in this method are executed one by one when we call the validate() orsave() method of the model instance

Note It is very important to remember that attributes appearing in rules() mustbe those to be entered by end users Other attributes such as id and create time

in the Post model which are set by our code or database should not be in rules()For more details please refer to Securing Attribute Assignments

After making these changes we can visit the post creation page again to verify that thenew validation rules are taking effect

312 Customizing relations() Method

Lastly we customize the relations() method to specify the related objects of a post Bydeclaring these related objects in relations() we can exploit the powerful RelationalActiveRecord (RAR) feature to access the related object information of a post such asits author and comments without the need to write complex SQL JOIN statements

We customize the relations() method as follows

31 Customizing Post Model 21

public function relations()

return array(

rsquoauthorrsquo =gt array(selfBELONGS TO rsquoUserrsquo rsquoauthor idrsquo)

rsquocommentsrsquo =gt array(selfHAS MANY rsquoCommentrsquo rsquopost idrsquo

rsquoconditionrsquo=gtrsquocommentsstatus=rsquoCommentSTATUS APPROVED

rsquoorderrsquo=gtrsquocommentscreate time DESCrsquo)

rsquocommentCountrsquo =gt array(selfSTAT rsquoCommentrsquo rsquopost idrsquo

rsquoconditionrsquo=gtrsquostatus=rsquoCommentSTATUS APPROVED)

)

The above relations state that

bull A post belongs to an author whose class is User and the relationship is establishedbased on the author id attribute value of the post

bull A post has many comments whose class is Comment and the relationship is establishedbased on the post id attribute value of the comments These comments should besorted according to their creation time and the comments must be approved

bull The commentCount relation is a bit special as it returns back an aggregation resultwhich is about how many comments the post has

With the above relation declaration we can easily access the author and comments of apost like the following

$author=$post-gtauthor

echo $author-gtusername

$comments=$post-gtcomments

foreach($comments as $comment)

echo $comment-gtcontent

For more details about how to declare and use relations please refer to the Guide

313 Representing Status in Text

Because the status of a post is stored as an integer in the database we need to provide atextual representation so that it is more intuitive when being displayed to end users In alarge system the similar requirement is very common

22 3 Post Management

As a generic solution we use the tbl lookup table to store the mapping between integervalues and textual representations that are needed by other data objects We modify theLookup model class as follows to more easily access the textual data in the table

class Lookup extends CActiveRecord

private static $ items=array()

public static function items($type)

if(isset(self$ items[$type]))

selfloadItems($type)

return self$ items[$type]

public static function item($type$code)

if(isset(self$ items[$type]))

selfloadItems($type)

return isset(self$ items[$type][$code]) self$ items[$type][$code] false

private static function loadItems($type)

self$ items[$type]=array()

$models=selfmodel()-gtfindAll(array(

rsquoconditionrsquo=gtrsquotype=typersquo

rsquoparamsrsquo=gtarray(rsquotypersquo=gt$type)

rsquoorderrsquo=gtrsquopositionrsquo

))

foreach($models as $model)

self$ items[$type][$model-gtcode]=$model-gtname

Our new code mainly provides two static methods Lookupitems() and Lookupitem()The former returns a list of strings belonging to the specified data type while the latterreturns a particular string for the given data type and data value

Our blog database is pre-populated with two lookup types PostStatus and CommentStatusThe former refers to the possible post statuses while the latter the comment statuses

In order to make our code easier to read we also declare a set of constants to representthe status integer values We should use these constants through our code when referringto the corresponding status values

32 Creating and Updating Posts 23

class Post extends CActiveRecord

const STATUS DRAFT=0

const STATUS PUBLISHED=1

const STATUS ARCHIVED=2

Therefore we can call Lookupitems(rsquoPostStatusrsquo) to get the list of possible post statuses(text strings indexed by the corresponding integer values) and call Lookupitem(rsquoPostStatusrsquoPostSTATUS PUBLISHED) to get the string representation of the published status

32 Creating and Updating Posts

With the Post model ready we need to fine-tune the actions and views for the controllerPostController In this section we first customize the access control of CRUD operationswe then modify the code implementing the create and update operations

321 Customizing Access Control

The first thing we want to do is to customize the access control because the code generatedby yiic does not fit our needs

We modify the accessRules() method in the file wwwrootblogprotectedcontrollers

PostControllerphp as follows

public function accessRules()

return array(

array(rsquoallowrsquo allow all users to perform rsquolistrsquo and rsquoshowrsquo actions

rsquoactionsrsquo=gtarray(rsquoindexrsquo rsquoviewrsquo)

rsquousersrsquo=gtarray(rsquorsquo)

)

array(rsquoallowrsquo allow authenticated users to perform any action

rsquousersrsquo=gtarray(rsquorsquo)

)

array(rsquodenyrsquo deny all users

rsquousersrsquo=gtarray(rsquorsquo)

)

)

The above rules state that all users can access the index and view actions and authenti-cated users can access any actions including the admin action The user should be denied

24 3 Post Management

access in any other scenario Note that these rules are evaluated in the order they arelisted here The first rule matching the current context makes the access decision Forexample if the current user is the system owner who tries to visit the post creation pagethe second rule will match and it will give the access to the user

322 Customizing create and update Operations

The create and update operations are very similar They both need to display an HTMLform to collect user inputs validate them and save them into database The main differ-ence is that the update operation will pre-populate the form with the existing post datafound in the database For this reason the yiic tool generates a partial view wwwroot

blogprotectedviewspost formphp that is embedded in both the create and update

views to render the needed HTML form

We first change the formphp file so that the HTML form only collects the inputs wewant title content tags and status We use plain text fields to collect inputs for thefirst three attributes and a dropdown list to collect input for status The dropdown listoptions are the text displays of the possible post statuses

ltphp echo CHtmlactiveDropDownList($postrsquostatusrsquoLookupitems(rsquoPostStatusrsquo)) gt

In the above we call Lookupitems(rsquoPostStatusrsquo) to bring back the list of post statuses

We then modify the Post class so that it can automatically set some attributes (eg create

time author id) before a post is saved to the database We override the beforeSave()

method as follows

protected function beforeSave()

if(parentbeforeSave())

if($this-gtisNewRecord)

$this-gtcreate time=$this-gtupdate time=time()

$this-gtauthor id=Yiiapp()-gtuser-gtid

else

$this-gtupdate time=time()

return true

else

return false

33 Displaying Posts 25

When we save a post we want to update the tbl tag table to reflect the change of tagfrequencies We can do this work in the afterSave() method which is automaticallyinvoked by Yii after a post is successfully saved into the database

protected function afterSave()

parentafterSave()

Tagmodel()-gtupdateFrequency($this-gt oldTags $this-gttags)

private $ oldTags

protected function afterFind()

parentafterFind()

$this-gt oldTags=$this-gttags

In the implementation because we want to detect if the user changes the tags in case he isupdating an existing post we need to know what the old tags are For this reason we alsowrite the afterFind() method to keep the old tags in the variable oldTags The methodafterFind() is invoked automatically by Yii when an AR record is populated with thedata from database

We are not going to give details of the TagupdateFrequency() method here Interestedreaders may refer to the file wwwrootyiidemosblogprotectedmodelsTagphp

33 Displaying Posts

In our blog application a post may be displayed among a list of posts or by itself Theformer is implemented as the index operation while the latter the view operation In thissection we customize both operations to fulfill our initial requirements

331 Customizing view Operation

The view operation is implemented by the actionView() method in PostController Itsdisplay is generated by the view view with the view file wwwrootblogprotectedviews

postviewphp

Below is the relevant code implementing the view operation in PostController

public function actionView()

26 3 Post Management

$this-gtrender(rsquoviewrsquoarray(

$post=$this-gtloadModel()

$this-gtrender(rsquoviewrsquoarray(

rsquomodelrsquo=gt$post

))

))

private $ model

public function loadModel()

if($this-gt model===null)

if(isset($ GET[rsquoidrsquo]))

if(Yiiapp()-gtuser-gtisGuest)

$condition=rsquostatus=rsquoPostSTATUS PUBLISHED

rsquo OR status=rsquoPOSTSTATUS ARCHIVED

else

$condition=rsquorsquo

$this-gt model=Postmodel()-gtfindbyPk($ GET[rsquoidrsquo] $condition)

if($this-gt model===null)

throw new CHttpException(404rsquoThe requested page does not existrsquo)

return $this-gt model

Our change mainly lies in the loadModel() method In this method we query the Post

table according to the id GET parameter If the post is not found or if it is not publishedor archived (when the user is a guest) we will throw a 404 HTTP error Otherwise thepost object is returned to actionView() which in turn passes the post object to the viewscript for further display

Tip Yii captures HTTP exceptions (instances of CHttpException) and displaysthem in either predefined templates or customized error views The skeleton appli-cation generated by yiic already contains a customized error view in wwwroot

blogprotectedviewssiteerrorphp We can modify this file if we want tofurther customize the error display

The change in the view script is mainly about ajdusting the formatting and styles of thepost display We will not go into details here Interested readers may refer to wwwroot

blogprotectedviewspostviewphp

33 Displaying Posts 27

332 Customizing index Operation

Like the view operation we customize the index operation in two places the actionIndex()

method in PostController and the view file wwwrootblogprotectedviewspostindex

php We mainly need to add the support for displaying a list of posts that are associatedwith a specified tag

Below is the modified actionIndex() method in PostController

public function actionIndex()

$criteria=new CDbCriteria(array(

rsquoconditionrsquo=gtrsquostatus=rsquoPostSTATUS PUBLISHED

rsquoorderrsquo=gtrsquoupdate time DESCrsquo

rsquowithrsquo=gtrsquocommentCountrsquo

))

if(isset($ GET[rsquotagrsquo]))

$criteria-gtaddSearchCondition(rsquotagsrsquo$ GET[rsquotagrsquo])

$dataProvider=new CActiveDataProvider(rsquoPostrsquo array(

rsquopaginationrsquo=gtarray(

rsquopageSizersquo=gtYiiapp()-gtparams[rsquopostsPerPagersquo]

)

rsquocriteriarsquo=gt$criteria

))

$this-gtrender(rsquoindexrsquoarray(

rsquodataProviderrsquo=gt$dataProvider

))

In the above we first create a query criteria for retrieving post list The criteria statesthat only published posts should be returned and they should be sorted according to theirupdate time in descending order Because when displaying a post in the list we want toshow how many comments the post has received in the criteria we also specify to bringback commentCount which if you remember is a relation declared in Postrelations()

In case when a user wants to see posts with a specific tag we would add a search conditionto the criteria to look for the specified tag

Using the query criteria we create a data provider which mainly serves for three purposesFirst it does pagination of the data when too many results may be returned Herewe customize the pagination by setting the page size to be an application parameterpostsPerPage Second it does sorting according to the user request And finally it feedsthe paginated and sorted data to widgets or view code for presentation

28 3 Post Management

After we finish with actionIndex() we modify the index view as follows Our change ismainly about adding the h1 header when the user specifies to display posts with a tag

ltphp if(empty($ GET[rsquotagrsquo])) gt

lth1gtPosts Tagged with ltigtltphp echo CHtmlencode($ GET[rsquotagrsquo]) gtltigtlth1gt

ltphp endif gt

ltphp $this-gtwidget(rsquoziiwidgetsCListViewrsquo array(

rsquodataProviderrsquo=gt$dataProvider

rsquoitemViewrsquo=gtrsquo viewrsquo

rsquotemplatersquo=gtitemsnpager)) gt

Note that in the above we use [CListView] to display the post list This widget requires apartial view to display the detail of each individual post Here we specify the partial viewto be view which means the file wwwrootblogprotectedviewspost viewphp In thisview script we can acccess the post instance being displayed via a local variable named$data

34 Managing Posts

Managing posts mainly refers to listing posts in an administrative view that allows us tosee posts with all statuses updating them and deleting them They are accomplished bythe admin operation and the delete operation respectively The code generated by yiic

does not need much modification Below we mainly explain how these two operations areimplemented

341 Listing Posts in Tabular View

The admin operation shows posts with all statuses in a tabular view The view supportssorting and pagination The following is the actionAdmin() method in PostController

public function actionAdmin()

$dataProvider=new CActiveDataProvider(rsquoPostrsquo array(

rsquosortrsquo=gtarray(

rsquodefaultOrderrsquo=gtrsquostatus update time DESCrsquo

)

))

$this-gtrender(rsquoadminrsquoarray(

rsquodataProviderrsquo=gt$dataProvider

))

34 Managing Posts 29

The above code is very similar to that in actionIndex() The main difference is in thequery criteria Here we do not specify any particular query criteria since we want to seeall posts We do customize the sort property of the data provider so that by default theposts are shown in the order of their statuses and update time

Below is the code for the admin view

lth1gtManage Postslth1gt

ltphp $this-gtwidget(rsquoziiwidgetsgridCGridViewrsquo array(

rsquodataProviderrsquo=gt$dataProvider

rsquocolumnsrsquo=gtarray(

array(

rsquonamersquo=gtrsquotitlersquo

rsquotypersquo=gtrsquorawrsquo

rsquovaluersquo=gtrsquoCHtmllink(CHtmlencode($data-gttitle) $data-gturl)rsquo

)

array(

rsquonamersquo=gtrsquostatusrsquo

rsquovaluersquo=gtrsquoLookupitem(PostStatus$data-gtstatus)rsquo

)

rsquocreate timedatetimersquo

rsquoupdate timedatetimersquo

array(

rsquoclassrsquo=gtrsquoCButtonColumnrsquo

)

)

)) gt

We use [CGridView] to display the posts It allows us to sort by a column and paginatethrough the posts if there are too many to be displayed in a single page Our change ismainly about how to display each column For example for the title column we specifythat it should be displayed as a hyperlink that points to the detailed view of the post

Tip When displaying text we call CHtmlencode() to encode HTML entities init This prevents from cross-site scripting attack

342 Deleting Posts

In the admin data grid there is a delete button in each row Clicking on the button shoulddelete the corresponding post Internally this triggers the delete action implemented asfollows

30 3 Post Management

public function actionDelete()

if(Yiiapp()-gtrequest-gtisPostRequest)

we only allow deletion via POST request

$this-gtloadModel()-gtdelete()

if(isset($ POST[rsquoajaxrsquo]))

$this-gtredirect(array(rsquoindexrsquo))

else

throw new CHttpException(400rsquoInvalid request Please do not repeat this request againrsquo)

The above code is the one generated by the yiic tool without any change We wouldlike to explain a little bit more about the checking on $ POST[rsquoajaxrsquo] The [CGridView]widget has a very nice feature that its sorting pagination and deletion operations are alldone in AJAX mode by default That means the whole page does not get reloaded if anyof the above operations is performed However it is also possible that the widget runs innon-AJAX mode (by setting its ajaxUpdate property to be false or disabling JavaScript onthe client side) It is necessary for the delete action to differentiate these two scenarios ifthe delete request is made via AJAX we should not redirect the user browser otherwisewe should

Deleting a post should also cause the deletion of all comments for that post In additionwe should also update the tbl tag table regarding the tags for the deleted post Both ofthese tasks can be achieved by writing an afterDelete method in the Post model class asfollows

protected function afterDelete()

parentafterDelete()

Commentmodel()-gtdeleteAll(rsquopost id=rsquo$this-gtid)

Tagmodel()-gtupdateFrequency($this-gttags rsquorsquo)

The above code is very straightforward it first deletes all those comments whose post id

is the same as the ID of the deleted post it then updates the tbl tag table for the tags

of the deleted post

34 Managing Posts 31

Tip We have to explicitly delete all comments for the deleted post here becauseSQLite does not really support foreign key constraints In a DBMS that supportsthis constraint (such as MySQL PostgreSQL) the foreign key constraint can be setup such that the DBMS automatically deletes the related comments if the post isdeleted In that case we no longer this explicit deletion call in our code

32 3 Post Management

Chapter 4

Comment Management

41 Customizing Comment Model

For the Comment model we mainly need to customize the rules() and attributeLabels()

methods The attributeLabels() method returns a mapping between attribute namesand attribute labels We do not need to touch relations() since the code generated bythe yiic tool is good enough

411 Customizing rules() Method

We first customize the validation rules generated by the yiic tool The following rules areused for comments

public function rules()

return array(

array(rsquocontent author emailrsquo rsquorequiredrsquo)

array(rsquoauthor email urlrsquo rsquolengthrsquo rsquomaxrsquo=gt128)

array(rsquoemailrsquorsquoemailrsquo)

array(rsquourlrsquorsquourlrsquo)

)

In the above we specify that the author email and content attributes are required thelength of author email and url cannot exceed 128 the email attribute must be a validemail address and the url attribute must be a valid URL

412 Customizing attributeLabels() Method

We then customize the attributeLabels() method to declare the label display for eachmodel attribute This method returns an array consisting of name-label pairs When wecall CHtmlactiveLabel() to display an attribute label

34 4 Comment Management

public function attributeLabels()

return array(

rsquoidrsquo =gt rsquoIdrsquo

rsquocontentrsquo =gt rsquoCommentrsquo

rsquostatusrsquo =gt rsquoStatusrsquo

rsquocreate timersquo =gt rsquoCreate Timersquo

rsquoauthorrsquo =gt rsquoNamersquo

rsquoemailrsquo =gt rsquoEmailrsquo

rsquourlrsquo =gt rsquoWebsitersquo

rsquopost idrsquo =gt rsquoPostrsquo

)

Tip If the label for an attribute is not declared in attributeLabels() an algo-rithm will be used to generate an appropriate label For example a label VerifyCode will be generated for attributes verify code or verifyCode

413 Customizing Saving Process

Because we want to record the creation time of a comment we override the beforeSave()

method of Comment like we do for the Post model

protected function beforeSave()

if(parentbeforeSave())

if($this-gtisNewRecord)

$this-gtcreate time=time()

return true

else

return false

42 Creating and Displaying Comments

In this section we implement the comment display and creation features

421 Displaying Comments

Instead of displaying and creating comments on individual pages we use the post detailpage (generated by the view action of PostController) Below the post content display

42 Creating and Displaying Comments 35

we display first a list of comments belonging to that post and then a comment creationform

In order to display comments on the post detail page we modify the view script wwwrootblogprotectedviewspostviewphp as follows

ltdiv id=commentsgt

ltphp if($model-gtcommentCountgt=1) gt

lth3gt

ltphp echo $model-gtcommentCount rsquocomment(s)rsquo gt

lth3gt

ltphp $this-gtrenderPartial(rsquo commentsrsquoarray(

rsquopostrsquo=gt$model

rsquocommentsrsquo=gt$model-gtcomments

)) gt

ltphp endif gt

ltdivgt

In the above we call renderPartial() to render a partial view named comments to displaythe list of comments belonging to the current post Note that in the view we use theexpression $model-gtcomments to retrieve the comments for the post This is valid becausewe have declared a comments relation in the Post class Evaluating this expression wouldtrigger an implicit JOIN database query to bring back the proper comments This featureis known as lazy relational query

The partial view comments is not very interesting It mainly goes through every commentand displays the detail of it Interested readers may refer to wwwrootyiidemosblog

protectedpost commentsphp

422 Creating Comments

To handle comment creation we first modify the actionView() method of PostController

as follows

public function actionView()

$post=$this-gtloadModel()

$comment=$this-gtnewComment($post)

$this-gtrender(rsquoviewrsquoarray(

rsquomodelrsquo=gt$post

rsquocommentrsquo=gt$comment

))

36 4 Comment Management

protected function newComment($post)

$comment=new Comment

if(isset($ POST[rsquoCommentrsquo]))

$comment-gtattributes=$ POST[rsquoCommentrsquo]

if($post-gtaddComment($comment))

if($comment-gtstatus==CommentSTATUS PENDING)

Yiiapp()-gtuser-gtsetFlash(rsquocommentSubmittedrsquorsquoThank yoursquo)

$this-gtrefresh()

return $comment

In the above we call the newComment() method before we render view In the newComment()

method we generate a Comment instance and check if the comment form is submitted If sowe try to add the comment for the post by calling $post-gtaddComment($comment) If it goesthrough we refresh the post detail page In case the comment needs to be approved we willshow a flash message to indicate this decision A flash message is usually a confirmationmessage displayed to end users If the user clicks on the refresh button of his browser themessage will disappear

We also need to modify wwwrootblogprotectedviewspostviewphp furthermore

ltdiv id=commentsgt

lth3gtLeave a Commentlth3gt

ltphp if(Yiiapp()-gtuser-gthasFlash(rsquocommentSubmittedrsquo)) gt

ltdiv class=successgt

ltphp echo Yiiapp()-gtuser-gtgetFlash(rsquocommentSubmittedrsquo) gt

ltdivgt

ltphp else gt

ltphp $this-gtrenderPartial(rsquocomment formrsquoarray(

rsquomodelrsquo=gt$comment

)) gt

ltphp endif gt

ltdivgtlt-- comments --gt

43 Managing Comments 37

In the above code we display the flash message if it is available If not we displaythe comment input form by rendering the partial view wwwrootblogprotectedviews

comment formphp

43 Managing Comments

Comment management includes updating deleting and approving comments These op-erations are implemented as actions in the CommentController class

431 Updating and Deleting Comments

The code generated by yiic for updating and deleting comments remains largely un-changed

432 Approving Comments

When comments are newly created they are in pending approval status and need to beapproved in order to be visible to guest users Approving a comment is mainly aboutchanging the status column of the comment

We create an actionApprove() method in CommentController as follows

public function actionApprove()

if(Yiiapp()-gtrequest-gtisPostRequest)

$comment=$this-gtloadModel()

$comment-gtapprove()

$this-gtredirect(array(rsquoindexrsquo))

else

throw new CHttpException(400rsquoInvalid requestrsquo)

In the above when the approve action is invoked via a POST request we call the approve()

method defined in the Comment model to change the status We then redirect the userbrowser to the page displaying the post that this comment belongs to

We also modify the actionIndex() method of Comment to show all comments We wouldlike to see comments pending approved to show up first

public function actionIndex()

38 4 Comment Management

$dataProvider=new CActiveDataProvider(rsquoCommentrsquo array(

rsquocriteriarsquo=gtarray(

rsquowithrsquo=gtrsquopostrsquo

rsquoorderrsquo=gtrsquotstatus tcreate time DESCrsquo

)

))

$this-gtrender(rsquoindexrsquoarray(

rsquodataProviderrsquo=gt$dataProvider

))

Notice that in the above code because both tbl post and tbl comment have columns statusand create time we need to disambiguate the corresponding column reference by prefixingthem with table alias names As described in the guide the alias for the primary table ina relational query is always t Therefore we are prefixing t to the status and create time

columns in the above code

Like the post index view the index view for CommentController uses [CListView] to displaythe comment list which in turn uses the partial view wwwrootblogprotectedviews

comment viewphp to display the detail of each individual comment We will not go intodetails here Interested readers may refer to the corresponding file in the blog demowwwrootyiidemosblogprotectedviewscomment viewphp

Chapter 5

Portlets

51 Customizing Page Layout

Features like rdquothe most recent commentsrdquo rdquotag cloudrdquo are better to be implemented inportlets A portlet is a pluggable user interface component that renders a fragment ofHTML code In this section we describe how to set up the portlet architecture for ourblog application

Before we go into details about developing portlets we first need to adjust our page layoutso that the portlets can be placed in a side bar

A page layout determines how to share common sections (eg header footer) amongdifferent pages and how to embed contents that are specific for different pages In Yiia page layout is represented as a view under the protectedviewslayouts folder Whenwe call the render() method in a controller the rendering result will be automaticallyembedded into this layout view before it is displayed to end users The default layoutview is named as main which means the wwwrootblogprotectedviewslayoutsmain

php file in our blog application

We could directly modify the main layout to add a side bar However doing so wouldmake some irrelevant pages such as about contact pages to have a side bar which is notalways desirable For this reason we need different layouts for different pages

We thus create two different layouts column1 and column2 The former displays the pagein a single column while the latter displays two columns in the page one of which is theside bar We will use column2 layout for the post and comment pages and column1 forthe rest of the pages Because both column1 and column2 still share the same header andfooter sections we let them to share the main layout

Based on the above description we create the column1 layout as follows

ltphp $this-gtbeginContent(rsquolayoutsmainrsquo) gt

ltdiv class=containergt

40 5 Portlets

ltdiv id=contentgt

ltphp echo $content gt

ltdivgtlt-- content --gt

ltdivgt

ltphp $this-gtendContent() gt

and the column2 layout as follows

ltphp $this-gtbeginContent(rsquolayoutsmainrsquo) gt

ltdiv class=containergt

ltdiv class=span-18gt

ltdiv id=contentgt

ltphp echo $content gt

ltdivgtlt-- content --gt

ltdivgt

ltdiv class=span-6 lastgt

ltdiv id=sidebargt

portlets to be placed here

ltdivgtlt-- sidebar --gt

ltdivgt

ltdivgt

ltphp $this-gtendContent() gt

As we can see both column1 and column2 are enclosed in a pair of beginContent andendContent calls They essentially use the specified view layoutsmain to decorate theenclosed content In all these layout views the variable $content refers to the dynamiccontent to be embedded into the layout

Besides customizing the layout view file we also need to adjust the CSS file wwwroot

blogcssmaincss so that the overall appearance would look like what we see in the blogdemo We will not go into details here

Info The skeleton application generated by the yiic tool uses the BluePrint CSSframework It provides a nice font reset and a robust grid system For more detailson how to use this framework please refer to its documentation

After creating the new layouts we need to set the layout property of controllers to makeuse of them So far we have three controllers SiteController PostController andCommentController We set SiteController to use column1 as the default layout whilethe rest two of the controllers use column2 for example

class PostController extends Controller

52 Creating User Menu Portlet 41

public $layout=rsquocolumn2rsquo

Tip If some view of the controller does not use the same layout as declaredby the layout property it may customize it in the view by a simple assignment$this-gtlayout=rsquonewlayoutrsquo

52 Creating User Menu Portlet

Based on the requirements analysis we need three different portlets the rdquouser menurdquoportlet the rdquotag cloudrdquo portlet and the rdquorecent commentsrdquo portlet We will develop theseportlets by extending the [CPortlet] widget provided by Yii

In this section we will develop our first concrete portlet - the user menu portlet whichdisplays a list of menu items that are only available to authenticated users The menucontains four items

bull Approve Comments a hyperlink that leads to a list of comments pending approval

bull Create New Post a hyperlink that leads to the post creation page

bull Manage Posts a hyperlink that leads to the post management page

bull Logout a link button that would log out the current user

521 Creating UserMenu Class

We create the UserMenu class to represent the logic part of the user menu portlet Theclass is saved in the file wwwrootblogprotectedcomponentsUserMenuphp which has thefollowing content

Yiiimport(rsquoziiwidgetsCPortletrsquo)

class UserMenu extends CPortlet

public function init()

$this-gttitle=CHtmlencode(Yiiapp()-gtuser-gtname)

parentinit()

42 5 Portlets

protected function renderContent()

$this-gtrender(rsquouserMenursquo)

The UserMenu class extends from the CPortlet class that we created previously It overridesboth the init() method and the renderContent() method of CPortlet The former setsthe portlet title to be the name of the current user the latter generates the portlet bodycontent by rendering a view named userMenu

Tip Notice that we have to explicitly include the CPortlet class by calling Yii

import() before we refer to it the first time This is because CPortlet is part of thezii project ndash the official extension library for Yii For performance considerationclasses in this project are not listed as core classes Therefore we have to import itbefore we use it the first time

522 Creating userMenu View

Next we create the userMenu view which is saved in the file wwwrootblogprotected

componentsviewsuserMenuphp

ltulgt

ltligtltphp echo CHtmllink(rsquoCreate New Postrsquoarray(rsquopostcreatersquo)) gtltligt

ltligtltphp echo CHtmllink(rsquoManage Postsrsquoarray(rsquopostadminrsquo)) gtltligt

ltligtltphp echo CHtmllink(rsquoApprove Commentsrsquoarray(rsquocommentindexrsquo))

rsquo (rsquo Commentmodel()-gtpendingCommentCount rsquo)rsquo gtltligt

ltligtltphp echo CHtmllink(rsquoLogoutrsquoarray(rsquositelogoutrsquo)) gtltligt

ltulgt

Info By default view files for a widget should be placed under the views sub-directory of the directory containing the widget class file The file name must bethe same as the view name

523 Using UserMenu Portlet

It is time for us to make use of our newly completed UserMenu portlet We modify thelayout view file wwwrootblogprotectedviewslayoutscolumn2php as follows

53 Creating Tag Cloud Portlet 43

ltdiv id=sidebargt

ltphp if(Yiiapp()-gtuser-gtisGuest) $this-gtwidget(rsquoUserMenursquo) gt

ltdivgt

In the above we call the widget() method to generate and execute an instance of theUserMenu class Because the portlet should only be displayed to authenticated users weonly call widget() when the isGuest property of the current user is false (meaning theuser is authenticated)

524 Testing UserMenu Portlet

Letrsquos test what we have so far

1 Open a browser window and enter the URL httpwwwexamplecomblogindex

php Verify that there is nothing displayed in the side bar section of the page

2 Click on the Login hyperlink and fill out the login form to login If successful verifythat the UserMenu portlet appears in the side bar and the portlet has the usernameas its title

3 Click on the rsquoLogoutrsquo hyperlink in the UserMenu portlet Verify that the logout actionis successful and the UserMenu portlet disappears

525 Summary

What we have created is a portlet that is highly reusable We can easily reuse it in adifferent project with little or no modification Moreover the design of this portlet followsclosely the philosophy that logic and presentation should be separated While we did notpoint this out in the previous sections such practice is used nearly everywhere in a typicalYii application

53 Creating Tag Cloud Portlet

Tag cloud displays a list of post tags with visual decorations hinting the popularity of eachindividual tag

531 Creating TagCloud Class

We create the TagCloud class in the file wwwrootblogprotectedcomponentsTagCloud

php The file has the following content

44 5 Portlets

Yiiimport(rsquoziiwidgetsCPortletrsquo)

class TagCloud extends CPortlet

public $title=rsquoTagsrsquo

public $maxTags=20

protected function renderContent()

$tags=Tagmodel()-gtfindTagWeights($this-gtmaxTags)

foreach($tags as $tag=gt$weight)

$link=CHtmllink(CHtmlencode($tag) array(rsquopostindexrsquorsquotagrsquo=gt$tag))

echo CHtmltag(rsquospanrsquo array(

rsquoclassrsquo=gtrsquotagrsquo

rsquostylersquo=gtfont-size$weightpt) $link)n

Unlike the UserMenu portlet the TagCloud portlet does not use a view Instead its presen-tation is done in the renderContent() method This is because the presentation does notcontain much HTML tags

We display each tag as a hyperlink to the post index page with the corresponding tagparameter The font size of each tag link is adjusted according to their relative weightamong other tags If a tag has higher frequency value than the other it will have a biggerfont size

532 Using TagCloud Portlet

Usage of the TagCloud portlet is very simple We modify the layout file wwwrootblog

protectedviewslayoutsmainphp as follows

ltdiv id=sidebargt

ltphp if(Yiiapp()-gtuser-gtisGuest) $this-gtwidget(rsquoUserMenursquo) gt

ltphp $this-gtwidget(rsquoTagCloudrsquo array(

rsquomaxTagsrsquo=gtYiiapp()-gtparams[rsquotagCloudCountrsquo]

)) gt

ltdivgt

54 Creating Recent Comments Portlet 45

54 Creating Recent Comments Portlet

In this section we create the last portlet that displays a list of comments recently pub-lished

541 Creating RecentComments Class

We create the RecentComments class in the file wwwrootblogprotectedcomponentsRecentCommentsphp The file has the following content

Yiiimport(rsquoziiwidgetsCPortletrsquo)

class RecentComments extends CPortlet

public $title=rsquoRecent Commentsrsquo

public $maxComments=10

public function getRecentComments()

return Commentmodel()-gtfindRecentComments($this-gtmaxComments)

protected function renderContent()

$this-gtrender(rsquorecentCommentsrsquo)

In the above we invoke the findRecentComments method which is defined in the Comment

class as follows

class Comment extends CActiveRecord

public function findRecentComments($limit=10)

return $this-gtwith(rsquopostrsquo)-gtfindAll(array(

rsquoconditionrsquo=gtrsquotstatus=rsquoselfSTATUS APPROVED

rsquoorderrsquo=gtrsquotcreate time DESCrsquo

rsquolimitrsquo=gt$limit

))

46 5 Portlets

542 Creating recentComments View

The recentComments view is saved in the file wwwrootblogprotectedcomponentsviews

recentCommentsphp It simply displays every comment returned by the RecentComments

getRecentComments() method

543 Using RecentComments Portlet

We modify the layout file wwwrootblogprotectedviewslayoutsmainphp to embedthis last portlet

ltdiv id=sidebargt

ltphp if(Yiiapp()-gtuser-gtisGuest) $this-gtwidget(rsquoUserMenursquo) gt

ltphp $this-gtwidget(rsquoTagCloudrsquo array(

rsquomaxTagsrsquo=gtYiiapp()-gtparams[rsquotagCloudCountrsquo]

)) gt

ltphp $this-gtwidget(rsquoRecentCommentsrsquo array(

rsquomaxCommentsrsquo=gtYiiapp()-gtparams[rsquorecentCommentCountrsquo]

)) gt

ltdivgt

Chapter 6

Final Work

61 Beautifying URLs

The URLs linking various pages of our blog application currently look ugly For examplethe URL for the page showing a post looks like the following

indexphpr=postshowampid=1

In this section we describe how to beautify these URLs and make them SEO-friendlyOur goal is to be able to use the following URLs in the application

1 indexphppostsyii leads to the page showing a list of posts with tag yii

2 indexphppost2A+Test+Post leads to the page showing the detail of the postwith ID 2 whose title is A Test Post

3 indexphppostupdate1 leads to the page that allows updating the post with ID1

Note that in the second URL format we include the post title in the URL This is mainlyto make the URL SEO friendly It is said that search engines may also respect the wordsfound in a URL when it is being indexed

To achieve our goal we modify the application configuration as follows

return array(

rsquocomponentsrsquo=gtarray(

rsquourlManagerrsquo=gtarray(

rsquourlFormatrsquo=gtrsquopathrsquo

rsquorulesrsquo=gtarray(

48 6 Final Work

rsquopostltidd+gtlttitlegtrsquo=gtrsquopostviewrsquorsquopostslttaggtrsquo=gtrsquopostindexrsquo

)

)

)

)

In the above we configure the urlManager component by setting its urlFormat propertyto be path and adding a set of rules

The rules are used by urlManager to parse and create the URLs in the desired formatFor example the first rule says that if a URL indexphppostsyii is requested theurlManager component should be responsible to dispatch the request to the route post

index and generate a tag GET parameter with the value yii On the other hand whencreating a URL with the route postindex and parameter tag the urlManager componentwill also use this rule to generate the desired URL indexphppostsyii For this reasonwe say that urlManager is a two-way URL manager

The urlManager component can further beautify our URLs such as hiding indexphp inthe URLs appending suffix like html to the URLs We can obtain these features easilyby configuring various properties of urlManager in the application configuration For moredetails please refer to the Guide

62 Logging Errors

A production Web application often needs sophisticated logging for various events In ourblog application we would like to log the errors occurring when it is being used Sucherrors could be programming mistakes or usersrsquo misuse of the system Logging these errorswill help us to improve the blog application

We enable the error logging by modifying the application configuration as follows

return array(

rsquopreloadrsquo=gtarray(rsquologrsquo)

rsquocomponentsrsquo=gtarray(

rsquologrsquo=gtarray(

rsquoclassrsquo=gtrsquoCLogRouterrsquo

rsquoroutesrsquo=gtarray(

array(

rsquoclassrsquo=gtrsquoCFileLogRoutersquo

rsquolevelsrsquo=gtrsquoerror warningrsquo

63 Final Tune-up and Deployment 49

)

)

)

)

)

With the above configuration if an error or warning occurs detailed information will belogged and saved in a file located under the directory wwwrootblogprotectedruntime

The log component offers more advanced features such as sending log messages to a listof email addresses displaying log messages in JavaScript console window etc For moredetails please refer to the Guide

63 Final Tune-up and Deployment

We are close to finish our blog application Before deployment we would like to do sometune-ups

631 Changing Home Page

We change to use the post list page as the home page We modify the application config-uration as follows

return array(

rsquodefaultControllerrsquo=gtrsquopostrsquo

)

Tip Because PostController already declares list to be its default action whenwe access the home page of the application we will see the result generated by thelist action of the post controller

632 Enabling Schema Caching

Because ActiveRecord relies on the metadata about tables to determine the column infor-mation it takes time to read the metadata and analyze it This may not be a problemduring development stage but for an application running in production mode it is a totalwaste of time if the database schema does not change Therefore we should enable theschema caching by modifying the application configuration as follows

50 6 Final Work

return array(

rsquocomponentsrsquo=gtarray(

rsquocachersquo=gtarray(

rsquoclassrsquo=gtrsquoCDbCachersquo

)

rsquodbrsquo=gtarray(

rsquoclassrsquo=gtrsquosystemdbCDbConnectionrsquo

rsquoconnectionStringrsquo=gtrsquosqlitewwwrootblogprotecteddatablogdbrsquo

rsquoschemaCachingDurationrsquo=gt3600

)

)

)

In the above we first add a cache component which uses a default SQLite database as thecaching storage If our server is equipped with other caching extensions such as APCwe could change to use them as well We also modify the db component by setting itsschemaCachingDuration property to be 3600 which means the parsed database schemadata can remain valid in cache for 3600 seconds

633 Disabling Debugging Mode

We modify the entry script file wwwrootblogindexphp by removing the line definingthe constant YII DEBUG This constant is useful during development stage because it allowsYii to display more debugging information when an error occurs However when theapplication is running in production mode displaying debugging information is not agood idea because it may contain sensitive information such as where the script file islocated and the content in the file etc

634 Deploying the Application

The final deployment process manly involves copying the directory wwwrootblog to thetarget directory The following checklist shows every needed step

1 Install Yii in the target place if it is not available

2 Copy the entire directory wwwrootblog to the target place

3 Edit the entry script file indexphp by pointing the $yii variable to the new Yiibootstrap file

4 Edit the file protectedyiicphp by setting the $yiic variable to be the new Yiiyiicphp file

64 Future Enhancements 51

5 Change the permission of the directories assets and protectedruntime so that theyare writable by the Web server process

64 Future Enhancements

641 Using a Theme

Without writing any code our blog application is already themeable To use a themewe mainly need to develop the theme by writing customized view files in the theme Forexample to use a theme named classic that uses a different page layout we would createa layout view file wwwrootblogthemesclassicviewslayoutsmainphp We also needto change the application configuration to indicate our choice of the classic theme

return array(

rsquothemersquo=gtrsquoclassicrsquo

)

642 Internationalization

We may also internationalize our blog application so that its pages can be displayed indifferent languages This mainly involves efforts in two aspects

First we may create view files in different languages For example for the list pageof PostController we can create a view file wwwrootblogprotectedviewspostzh cn

listphp When the application is configured to use simplified Chinese (the language codeis zh cn) Yii will automatically use this new view file instead of the original one

Second we may create message translations for those messages generated by code Themessage translations should be saved as files under the directory wwwrootblogprotected

messages We also need to modify the code where we use text strings by enclosing themin the method call Yiit()

For more details about internationalization please refer to the Guide

643 Improving Performance with Cache

While the Yii framework itself is very efficient it is not necessarily true that an applicationwritten in Yii efficient There are several places in our blog application that we can improvethe performance For example the tag clould portlet could be one of the performancebottlenecks because it involves complex database query and PHP logic

52 6 Final Work

We can make use of the sophisticated caching feature provided by Yii to improve theperformance One of the most useful components in Yii is COutputCache which caches afragment of page display so that the underlying code generating the fragment does not needto be executed for every request For example in the layout file wwwrootblogprotected

viewslayoutsmainphp we can enclose the tag cloud portlet with COutputCache

ltphp if($this-gtbeginCache(rsquotagCloudrsquo array(rsquodurationrsquo=gt3600))) gt

ltphp $this-gtwidget(rsquoTagCloudrsquo array(

rsquomaxTagsrsquo=gtYiiapp()-gtparams[rsquotagCloudCountrsquo]

)) gt

ltphp $this-gtendCache() gt

With the above code the tag cloud display will be served from cache instead of beinggenerated on-the-fly for every request The cached content will remain valid in cache for3600 seconds

644 Adding New Features

Our blog application only has very basic functionalities To become a complete blogsystem more features are needed for example calendar portlet email notifications postcategorization archived post portlet and so on We will leave the implementation of thesefeatures to interested readers

  • Contents
  • License
  • Getting Started
    • Building a Blog System using Yii
    • Testdriving with Yii
      • Installing Yii
      • Creating Skeleton Application
      • Application Workflow
        • Requirements Analysis
        • Overall Design
          • Initial Prototyping
            • Setting Up Database
              • Creating Database
              • Establishing Database Connection
                • Scaffolding
                • Authenticating User
                • Summary
                  • Post Management
                    • Customizing Post Model
                      • Customizing rules() Method
                      • Customizing relations() Method
                      • Representing Status in Text
                        • Creating and Updating Posts
                          • Customizing Access Control
                          • Customizing create and update Operations
                            • Displaying Posts
                              • Customizing view Operation
                              • Customizing index Operation
                                • Managing Posts
                                  • Listing Posts in Tabular View
                                  • Deleting Posts
                                      • Comment Management
                                        • Customizing Comment Model
                                          • Customizing rules() Method
                                          • Customizing attributeLabels() Method
                                          • Customizing Saving Process
                                            • Creating and Displaying Comments
                                              • Displaying Comments
                                              • Creating Comments
                                                • Managing Comments
                                                  • Updating and Deleting Comments
                                                  • Approving Comments
                                                      • Portlets
                                                        • Customizing Page Layout
                                                        • Creating User Menu Portlet
                                                          • Creating UserMenu Class
                                                          • Creating userMenu View
                                                          • Using UserMenu Portlet
                                                          • Testing UserMenu Portlet
                                                          • Summary
                                                            • Creating Tag Cloud Portlet
                                                              • Creating TagCloud Class
                                                              • Using TagCloud Portlet
                                                                • Creating Recent Comments Portlet
                                                                  • Creating RecentComments Class
                                                                  • Creating recentComments View
                                                                  • Using RecentComments Portlet
                                                                      • Final Work
                                                                        • Beautifying URLs
                                                                        • Logging Errors
                                                                        • Final Tune-up and Deployment
                                                                          • Changing Home Page
                                                                          • Enabling Schema Caching
                                                                          • Disabling Debugging Mode
                                                                          • Deploying the Application
                                                                            • Future Enhancements
                                                                              • Using a Theme
                                                                              • Internationalization
                                                                              • Improving Performance with Cache
                                                                              • Adding New Features
Page 13: Yii Blog 1.1.0 Demo Tutor

6 1 Getting Started

ndash name the textual representation of the data item that is to be displayed to endusers

ndash code the integer representation of the data item

ndash type the type of the data item

ndash position the relative display order of the data item among other items of thesame type

The following entity-relation (ER) diagram shows the table structure and relationshipsabout the above tables

Figure 11 Entity-Relation Diagram of the Blog Database

Complete SQL statements corresponding to the above ER diagram may be found inthe blog demo In our Yii installation they are in the file wwwrootyiidemosblog

protecteddataschemasqlitesql

Info We name all our table names and column names in lower case This is becausedifferent DBMS often have different case-sensitivity treatment and we want to avoidtroubles like this

We also prefix all our tables with tbl This serves for two purposes First the prefixintroduces a namespace to these tables in case when they need to coexist with othertables in the same database which often happens in a shared hosting environmentwhere a single database is being used by multiple applications Second using tableprefix reduces the possibility of having some table names that are reserved keywordsin DBMS

14 Overall Design 7

We divide the development of our blog application into the following milestones

bull Milestone 1 creating a prototype of the blog system It should consist of most ofthe required functionalities

bull Milestone 2 completing post management It includes creating listing showingupdating and deleting posts

bull Milestone 3 completing comment management It includes creating listing ap-proving updating and deleting post comments

bull Milestone 4 implementing portlets It includes user menu login tag cloud andrecent comments portlets

bull Milestone 5 final tune-up and deployment

8 1 Getting Started

Chapter 2

Initial Prototyping

21 Setting Up Database

Having created a skeleton application and finished the database design in this section wewill create the blog database and establish the connection to it in the skeleton application

211 Creating Database

We choose to create a SQLite database Because the database support in Yii is built on topof PDO we can easily switch to use a different type of DBMS (eg MySQL PostgreSQL)without the need to change our application code

We create the database file blogdb under the directory wwwrootblogprotecteddataNote that both the directory and the database file have to be writable by the Web serverprocess as required by SQLite We may simply copy the database file from the blogdemo in our Yii installation which is located at wwwrootyiidemosblogprotecteddatablogdb We may also generate the database by executing the SQL statements in the filewwwrootyiidemosblogprotecteddataschemasqlitesql

Tip To execute SQL statements we may use the sqlite3 command line tool thatcan be found in the SQLite official website

212 Establishing Database Connection

To use the blog database in the skeleton application we created we need to modify itsapplication configuration which is stored in the PHP script wwwrootblogprotected

configmainphp The script returns an associative array consisting of name-value pairseach of which is used to initialize a writable property of the application instance

We configure the db component as follows

10 2 Initial Prototyping

return array(

rsquocomponentsrsquo=gtarray(

rsquodbrsquo=gtarray(

rsquoconnectionStringrsquo=gtrsquosqlitewwwrootblogprotecteddatablogdbrsquo

rsquotablePrefixrsquo=gtrsquotbl rsquo

)

)

)

The above configuration says that we have a db application component whose connectionStringproperty should be initialized as sqlitewwwrootblogprotecteddatablogdb and whosetablePrefix property should be tbl

With this configuration we can access the DB connection object using Yiiapp()-gtdb

at any place in our code Note that Yiiapp() returns the application instance that wecreate in the entry script If you are interested in possible methods and properties that theDB connection has you may refer to its class reference However in most cases we are notgoing to use this DB connection directly Instead we will use the so-called ActiveRecordto access the database

We would like to explain a bit more about the tablePrefix property that we set in theconfiguration This tells the db connection that it should respect the fact we are usingtbl as the prefix to our database table names In particular if in a SQL statement thereis a token enclosed within double curly brackets (eg post then the db connectionshould translate it into a name with the table prefix (eg tbl post) before sending it toDBMS for execution This feature is especially useful if in future we need to modify thetable name prefix without touching our source code For example if we are developing ageneric content management system (CMS) we may exploit this feature so that when itis being installed in a new environment we can allow users to choose a table prefix theylike

22 Scaffolding 11

Tip If you want to use MySQL instead of SQLite to store data you may cre-ate a MySQL database named blog using the SQL statements in wwwrootyii

demosblogprotecteddataschemamysqlsql Then modify the applicationconfiguration as follows

return array(

rsquocomponentsrsquo=gtarray(

rsquodbrsquo=gtarray(

rsquoconnectionStringrsquo =gt rsquomysqlhost=localhostdbname=blogrsquo

rsquoemulatePreparersquo =gt true

rsquousernamersquo =gt rsquorootrsquo

rsquopasswordrsquo =gt rsquorsquo

rsquocharsetrsquo =gt rsquoutf8rsquo

rsquotablePrefixrsquo =gt rsquotbl rsquo

)

)

)

22 Scaffolding

Create read update and delete (CRUD) are the four basic operations of data objects in anapplication Because the task of implementing the CRUD operations is so common whendeveloping Web applications Yii provides a tool to automate this process (also known asscaffolding) In this section we will describe how to use the tool to implement CRUD forposts and comments

Open a command window and run the following commands

wwwrootyiiframeworkyiic shell wwwrootblogindexphp

Yii Interactive Tool v11

Please type rsquohelprsquo for help Type rsquoexitrsquo to quit

gtgt model

gtgt crud Post

gtgt crud Comment

gtgt exit

12 2 Initial Prototyping

Info Some PHP installations may use a different phpini file for command line(CLI) PHP parser As a result when running the above yiic commands youmay encounter errors like rdquoYiiBaseinclude(PDOphp) failed to open streamrdquoor rdquocould not find driverrdquo Please double check your CLI PHP configuration byexecuting the following command

php -r phpinfo()

The result of the above command will show which phpini file is being used andwhich extensions are loaded If a wrong phpini file is used you may use thefollowing command to explicitly specify the correct phpini to use

php -c phpini wwwrootyiiframeworkyiicphp shell wwwrootblogindexphp

The commands above accomplish two tasks First the model command generates a modelclass file for each table in the blog database Second the crud commands generate thecode needed by the CRUD operations for the Post and Comment models

Tip The command model generates a model class for every table in the databaseSometimes we probably donrsquot want to do so (eg the database contains some irrel-evant tables) In this case we can create model classes one by one For exampleto create the User model we can use the command model User The model com-mand also has some more advanced usages For more details execute help model

command

We can test the generated code by accessing the following URLs

httpwwwexamplecomblogindexphpr=post

httpwwwexamplecomblogindexphpr=comment

Notice that the post and comment features implemented by the generated code are com-pletely independent of each other Also when creating a new post or comment we arerequired to enter information such as author id and create time which in real applica-tion should be set by the program Donrsquot worry We will fix these problems in the nextmilestones For now we should be fairly satisfied as this prototype already contains mostfeatures that we need to implement for the blog application

To prepare for the next milestones letrsquos take a closer look at the files generated by theabove commands All the files are generated under wwwrootblogprotected For conve-nience we group them into model files controller files and view files

22 Scaffolding 13

bull model files

ndash modelsUserphp contains the User class that extends from CActiveRecord andcan be used to access the tbl user database table

ndash modelsPostphp contains the Post class that extends from CActiveRecord andcan be used to access the tbl post database table

ndash modelsTagphp contains the Tag class that extends from CActiveRecord andcan be used to access the tbl tag database table

ndash modelsCommentphp contains the Comment class that extends from CActiveRe-cord and can be used to access the tbl comment database table

ndash modelsLookupphp contains the Lookup class that extends from CActiveRecordand can be used to access the tbl lookup database table

bull controller file

ndash controllersPostControllerphp contains the PostController class which is thecontroller in charge of all CRUD operations about posts

ndash controllersCommentControllerphp contains the CommentController class whichis the controller in charge of all CRUD operations about comments

bull view files

ndash viewspostcreatephp is the view file that shows an HTML form to create anew post

ndash viewspostupdatephp is the view file that shows an HTML form to update anexisting post

ndash viewspostviewphp is the view file that displays the detailed information of apost

ndash viewspostindexphp is the view file that displays a list of posts

ndash viewspostadminphp is the view file that displays posts in a table with ad-ministrative commands

ndash viewspost formphp is the partial view file embedded in viewspostcreate

php and viewspostupdatephp It displays the HTML form for collecting postinformation

ndash viewspost viewphp is the partial view file used by viewspostindexphp Itdisplays the brief view of a single post

ndash a similar set of view files are also generated for comment

14 2 Initial Prototyping

In order to understand better how the above files are used we show in the following theworkflow that occurs in the blog application when displaying a list of posts

1 The user requests the URL httpwwwexamplecomblogindexphpr=posts

2 The entry script is executed by the Web server which creates and initializes anapplication instance to handle the request

3 The application creates an instance of PostController and executes it

4 The PostController instance executes the index action by calling its actionIndex()

method Note that index is the default action if the user does not specify an actionto execute in the URL

5 The actionIndex() method queries database to bring back the list of recent posts

6 The actionIndex() method renders the index view with the post data

23 Authenticating User

Our blog application needs to differentiate between the system owner and guest usersTherefore we need to implement the user authentication feature

As you may have found that the skeleton application already provides user authenticationby checking if the username and password are both demo or admin In this section wewill modify the corresponding code so that the authentication is done against the User

database table

User authentication is performed in a class implementing the IUserIdentity interface Theskeleton application uses the UserIdentity class for this purpose The class is stored inthe file wwwrootblogprotectedcomponentsUserIdentityphp

Tip By convention the name of a class file must be the same as the correspondingclass name suffixed with the extension php Following this convention one canrefer to a class using a path alias For example we can refer to the UserIdentity

class with the alias applicationcomponentsUserIdentity Many APIs in Yiican recognize path aliases (eg YiicreateComponent()) and using path aliasesavoids the necessity of embedding absolute file paths in the code The existence ofthe latter often causes trouble when we deploy an application

We modify the UserIdentity class as follows

23 Authenticating User 15

ltphp

class UserIdentity extends CUserIdentity

private $ id

public function authenticate()

$username=strtolower($this-gtusername)

$user=Usermodel()-gtfind(rsquoLOWER(username)=rsquoarray($username))

if($user===null)

$this-gterrorCode=selfERROR USERNAME INVALID

else if($user-gtvalidatePassword($this-gtpassword))

$this-gterrorCode=selfERROR PASSWORD INVALID

else

$this-gt id=$user-gtid

$this-gtusername=$user-gtusername

$this-gterrorCode=selfERROR NONE

return $this-gterrorCode==selfERROR NONE

public function getId()

return $this-gt id

In the authenticate() method we use the User class to look for a row in the User tablewhose username column is the same as the given username in a case-insensitive mannerRemember that the User class was created using the yiic tool in the prior section Becausethe User class extends from CActiveRecord we can exploit the ActiveRecord feature toaccess the User table in an OOP fashion

In order to check if the user has entered a valid password we invoke the validatePassword

method of the User class We need to modify the file wwwrootblogprotectedmodels

Userphp as follows Note that instead of storing the plain password in the database westore the hash result of the password and a randomly generated salt key When validatingthe user-entered password we should compare the hash results instead

class User extends CActiveRecord

public function validatePassword($password)

return $this-gthashPassword($password$this-gtsalt)===$this-gtpassword

16 2 Initial Prototyping

public function hashPassword($password$salt)

return md5($salt$password)

In the UserIdentity class we also override the getId() method which returns the id valueof the user found in the User table The parent implementation would return the usernameinstead Both the username and id properties will be stored in the user session and maybe accessed via Yiiapp()-gtuser from anywhere in our code

Tip In the UserIdentity class we reference the class CUserIdentity without ex-plicitly including the corresponding class file This is because CUserIdentity is acore class provided by the Yii framework Yii will automatically include the classfile for any core class when it is referenced for the first time

We also do the same with the User class This is because the User class file is placedunder the directory wwwrootblogprotectedmodels which has been added tothe PHP include path according to the following lines found in the applicationconfiguration

return array(

rsquoimportrsquo=gtarray(

rsquoapplicationmodelsrsquo

rsquoapplicationcomponentsrsquo

)

)

The above configuration says that any class whose class file is located under either wwwrootblogprotectedmodels or wwwrootblogprotectedcomponents willbe automatically included when the class is referenced for the first time

The UserIdentity class is mainly used by the LoginForm class to authenticate a user basedon the username and password input collected from the login page The following codefragment shows how UserIdentity is used

$identity=new UserIdentity($username$password)

$identity-gtauthenticate()

switch($identity-gterrorCode)

24 Summary 17

case UserIdentityERROR NONE

Yiiapp()-gtuser-gtlogin($identity)

break

Info People often get confused about identity and the user application componentThe former represents a way of performing authentication while the latter is usedto represent the information related with the current user An application can onlyhave one user component but it can have one or several identity classes dependingon what kind of authentication it supports Once authenticated an identity instancemay pass its state information to the user component so that they are globallyaccessible via user

To test the modified UserIdentity class we can browse the URL httpwwwexample

comblogindexphp and try logging in with the username and password that we store inthe User table If we use the database provided by the blog demo we should be able tologin with username demo and password demo Note that this blog system does not providethe user management feature As a result a user cannot change his account or create anew one through the Web interface The user management feature may be considered asa future enhancement to the blog application

24 Summary

We have completed the milestone 1 Letrsquos summarize what we have done so far

1 We identified the requirements to be fulfilled

2 We installed the Yii framework

3 We created a skeleton application

4 We designed and created the blog database

5 We modified the application configuration by adding the database connection

6 We generated the code that implements the basic CRUD operations for both postsand comments

7 We modified the authentication method to check against the tbl user table

18 2 Initial Prototyping

For a new project most of the time will be spent in step 1 and 4 for this first milestone

Although the code generated by the yiic tool implements fully functional CRUD oper-ations for a database table it often needs to be modified in practical applications Forthis reason in the next two milestone our job is to customize the generated CRUD codeabout posts and comments so that it reaches our initial requirements

In general we first modify the model class file by adding appropriate validation rules anddeclaring relational objects We then modify the controller action and view code for eachindividual CRUD operation

Chapter 3

Post Management

31 Customizing Post Model

The Post model class generated by the yiic tool mainly needs to be modified in two places

bull the rules() method specifies the validation rules for the model attributes

bull the relations() method specifies the related objects

Info A model consists of a list of attributes each associated with a column in thecorresponding database table Attributes can be declared explicitly as class membervariables or implicitly without any declaration

311 Customizing rules() Method

We first specify the validation rules which ensure the attribute values entered by usersare correct before they are saved to the database For example the status attribute ofPost should be an integer 1 2 or 3 The yiic tool also generates validation rules for eachmodel However these rules are based on the table column information and may not beappropriate

Based on the requirement analysis we modify the rules() method as follows

public function rules()

return array(

array(rsquotitle content statusrsquo rsquorequiredrsquo)

array(rsquotitlersquo rsquolengthrsquo rsquomaxrsquo=gt128)

array(rsquostatusrsquo rsquoinrsquo rsquorangersquo=gtarray(123))

array(rsquotagsrsquo rsquomatchrsquo rsquopatternrsquo=gtrsquo^[ws]+$rsquorsquomessagersquo=gtrsquoTags can only contain word charactersrsquo)

20 3 Post Management

array(rsquotagsrsquo rsquonormalizeTagsrsquo)

)

In the above we specify that the title content and status attributes are required thelength of title should not exceed 128 the status attribute value should be 1 (draft) 2(published) or 3 (archived) and the tags attribute should only contain word charactersand commas In addition we use normalizeTags to normalize the user-entered tags so thatthe tags are unique and properly separated with commas

The validators such as required length in and match are all built-in validators providedby Yii The normalizeTags validator is a method-based validator that we need to definein the Post class For more information about how to specify validation rules please referto the Guide

public function normalizeTags($attribute$params)

$this-gttags=Tagarray2string(array unique(Tagstring2array($this-gttags)))

The rules declared in this method are executed one by one when we call the validate() orsave() method of the model instance

Note It is very important to remember that attributes appearing in rules() mustbe those to be entered by end users Other attributes such as id and create time

in the Post model which are set by our code or database should not be in rules()For more details please refer to Securing Attribute Assignments

After making these changes we can visit the post creation page again to verify that thenew validation rules are taking effect

312 Customizing relations() Method

Lastly we customize the relations() method to specify the related objects of a post Bydeclaring these related objects in relations() we can exploit the powerful RelationalActiveRecord (RAR) feature to access the related object information of a post such asits author and comments without the need to write complex SQL JOIN statements

We customize the relations() method as follows

31 Customizing Post Model 21

public function relations()

return array(

rsquoauthorrsquo =gt array(selfBELONGS TO rsquoUserrsquo rsquoauthor idrsquo)

rsquocommentsrsquo =gt array(selfHAS MANY rsquoCommentrsquo rsquopost idrsquo

rsquoconditionrsquo=gtrsquocommentsstatus=rsquoCommentSTATUS APPROVED

rsquoorderrsquo=gtrsquocommentscreate time DESCrsquo)

rsquocommentCountrsquo =gt array(selfSTAT rsquoCommentrsquo rsquopost idrsquo

rsquoconditionrsquo=gtrsquostatus=rsquoCommentSTATUS APPROVED)

)

The above relations state that

bull A post belongs to an author whose class is User and the relationship is establishedbased on the author id attribute value of the post

bull A post has many comments whose class is Comment and the relationship is establishedbased on the post id attribute value of the comments These comments should besorted according to their creation time and the comments must be approved

bull The commentCount relation is a bit special as it returns back an aggregation resultwhich is about how many comments the post has

With the above relation declaration we can easily access the author and comments of apost like the following

$author=$post-gtauthor

echo $author-gtusername

$comments=$post-gtcomments

foreach($comments as $comment)

echo $comment-gtcontent

For more details about how to declare and use relations please refer to the Guide

313 Representing Status in Text

Because the status of a post is stored as an integer in the database we need to provide atextual representation so that it is more intuitive when being displayed to end users In alarge system the similar requirement is very common

22 3 Post Management

As a generic solution we use the tbl lookup table to store the mapping between integervalues and textual representations that are needed by other data objects We modify theLookup model class as follows to more easily access the textual data in the table

class Lookup extends CActiveRecord

private static $ items=array()

public static function items($type)

if(isset(self$ items[$type]))

selfloadItems($type)

return self$ items[$type]

public static function item($type$code)

if(isset(self$ items[$type]))

selfloadItems($type)

return isset(self$ items[$type][$code]) self$ items[$type][$code] false

private static function loadItems($type)

self$ items[$type]=array()

$models=selfmodel()-gtfindAll(array(

rsquoconditionrsquo=gtrsquotype=typersquo

rsquoparamsrsquo=gtarray(rsquotypersquo=gt$type)

rsquoorderrsquo=gtrsquopositionrsquo

))

foreach($models as $model)

self$ items[$type][$model-gtcode]=$model-gtname

Our new code mainly provides two static methods Lookupitems() and Lookupitem()The former returns a list of strings belonging to the specified data type while the latterreturns a particular string for the given data type and data value

Our blog database is pre-populated with two lookup types PostStatus and CommentStatusThe former refers to the possible post statuses while the latter the comment statuses

In order to make our code easier to read we also declare a set of constants to representthe status integer values We should use these constants through our code when referringto the corresponding status values

32 Creating and Updating Posts 23

class Post extends CActiveRecord

const STATUS DRAFT=0

const STATUS PUBLISHED=1

const STATUS ARCHIVED=2

Therefore we can call Lookupitems(rsquoPostStatusrsquo) to get the list of possible post statuses(text strings indexed by the corresponding integer values) and call Lookupitem(rsquoPostStatusrsquoPostSTATUS PUBLISHED) to get the string representation of the published status

32 Creating and Updating Posts

With the Post model ready we need to fine-tune the actions and views for the controllerPostController In this section we first customize the access control of CRUD operationswe then modify the code implementing the create and update operations

321 Customizing Access Control

The first thing we want to do is to customize the access control because the code generatedby yiic does not fit our needs

We modify the accessRules() method in the file wwwrootblogprotectedcontrollers

PostControllerphp as follows

public function accessRules()

return array(

array(rsquoallowrsquo allow all users to perform rsquolistrsquo and rsquoshowrsquo actions

rsquoactionsrsquo=gtarray(rsquoindexrsquo rsquoviewrsquo)

rsquousersrsquo=gtarray(rsquorsquo)

)

array(rsquoallowrsquo allow authenticated users to perform any action

rsquousersrsquo=gtarray(rsquorsquo)

)

array(rsquodenyrsquo deny all users

rsquousersrsquo=gtarray(rsquorsquo)

)

)

The above rules state that all users can access the index and view actions and authenti-cated users can access any actions including the admin action The user should be denied

24 3 Post Management

access in any other scenario Note that these rules are evaluated in the order they arelisted here The first rule matching the current context makes the access decision Forexample if the current user is the system owner who tries to visit the post creation pagethe second rule will match and it will give the access to the user

322 Customizing create and update Operations

The create and update operations are very similar They both need to display an HTMLform to collect user inputs validate them and save them into database The main differ-ence is that the update operation will pre-populate the form with the existing post datafound in the database For this reason the yiic tool generates a partial view wwwroot

blogprotectedviewspost formphp that is embedded in both the create and update

views to render the needed HTML form

We first change the formphp file so that the HTML form only collects the inputs wewant title content tags and status We use plain text fields to collect inputs for thefirst three attributes and a dropdown list to collect input for status The dropdown listoptions are the text displays of the possible post statuses

ltphp echo CHtmlactiveDropDownList($postrsquostatusrsquoLookupitems(rsquoPostStatusrsquo)) gt

In the above we call Lookupitems(rsquoPostStatusrsquo) to bring back the list of post statuses

We then modify the Post class so that it can automatically set some attributes (eg create

time author id) before a post is saved to the database We override the beforeSave()

method as follows

protected function beforeSave()

if(parentbeforeSave())

if($this-gtisNewRecord)

$this-gtcreate time=$this-gtupdate time=time()

$this-gtauthor id=Yiiapp()-gtuser-gtid

else

$this-gtupdate time=time()

return true

else

return false

33 Displaying Posts 25

When we save a post we want to update the tbl tag table to reflect the change of tagfrequencies We can do this work in the afterSave() method which is automaticallyinvoked by Yii after a post is successfully saved into the database

protected function afterSave()

parentafterSave()

Tagmodel()-gtupdateFrequency($this-gt oldTags $this-gttags)

private $ oldTags

protected function afterFind()

parentafterFind()

$this-gt oldTags=$this-gttags

In the implementation because we want to detect if the user changes the tags in case he isupdating an existing post we need to know what the old tags are For this reason we alsowrite the afterFind() method to keep the old tags in the variable oldTags The methodafterFind() is invoked automatically by Yii when an AR record is populated with thedata from database

We are not going to give details of the TagupdateFrequency() method here Interestedreaders may refer to the file wwwrootyiidemosblogprotectedmodelsTagphp

33 Displaying Posts

In our blog application a post may be displayed among a list of posts or by itself Theformer is implemented as the index operation while the latter the view operation In thissection we customize both operations to fulfill our initial requirements

331 Customizing view Operation

The view operation is implemented by the actionView() method in PostController Itsdisplay is generated by the view view with the view file wwwrootblogprotectedviews

postviewphp

Below is the relevant code implementing the view operation in PostController

public function actionView()

26 3 Post Management

$this-gtrender(rsquoviewrsquoarray(

$post=$this-gtloadModel()

$this-gtrender(rsquoviewrsquoarray(

rsquomodelrsquo=gt$post

))

))

private $ model

public function loadModel()

if($this-gt model===null)

if(isset($ GET[rsquoidrsquo]))

if(Yiiapp()-gtuser-gtisGuest)

$condition=rsquostatus=rsquoPostSTATUS PUBLISHED

rsquo OR status=rsquoPOSTSTATUS ARCHIVED

else

$condition=rsquorsquo

$this-gt model=Postmodel()-gtfindbyPk($ GET[rsquoidrsquo] $condition)

if($this-gt model===null)

throw new CHttpException(404rsquoThe requested page does not existrsquo)

return $this-gt model

Our change mainly lies in the loadModel() method In this method we query the Post

table according to the id GET parameter If the post is not found or if it is not publishedor archived (when the user is a guest) we will throw a 404 HTTP error Otherwise thepost object is returned to actionView() which in turn passes the post object to the viewscript for further display

Tip Yii captures HTTP exceptions (instances of CHttpException) and displaysthem in either predefined templates or customized error views The skeleton appli-cation generated by yiic already contains a customized error view in wwwroot

blogprotectedviewssiteerrorphp We can modify this file if we want tofurther customize the error display

The change in the view script is mainly about ajdusting the formatting and styles of thepost display We will not go into details here Interested readers may refer to wwwroot

blogprotectedviewspostviewphp

33 Displaying Posts 27

332 Customizing index Operation

Like the view operation we customize the index operation in two places the actionIndex()

method in PostController and the view file wwwrootblogprotectedviewspostindex

php We mainly need to add the support for displaying a list of posts that are associatedwith a specified tag

Below is the modified actionIndex() method in PostController

public function actionIndex()

$criteria=new CDbCriteria(array(

rsquoconditionrsquo=gtrsquostatus=rsquoPostSTATUS PUBLISHED

rsquoorderrsquo=gtrsquoupdate time DESCrsquo

rsquowithrsquo=gtrsquocommentCountrsquo

))

if(isset($ GET[rsquotagrsquo]))

$criteria-gtaddSearchCondition(rsquotagsrsquo$ GET[rsquotagrsquo])

$dataProvider=new CActiveDataProvider(rsquoPostrsquo array(

rsquopaginationrsquo=gtarray(

rsquopageSizersquo=gtYiiapp()-gtparams[rsquopostsPerPagersquo]

)

rsquocriteriarsquo=gt$criteria

))

$this-gtrender(rsquoindexrsquoarray(

rsquodataProviderrsquo=gt$dataProvider

))

In the above we first create a query criteria for retrieving post list The criteria statesthat only published posts should be returned and they should be sorted according to theirupdate time in descending order Because when displaying a post in the list we want toshow how many comments the post has received in the criteria we also specify to bringback commentCount which if you remember is a relation declared in Postrelations()

In case when a user wants to see posts with a specific tag we would add a search conditionto the criteria to look for the specified tag

Using the query criteria we create a data provider which mainly serves for three purposesFirst it does pagination of the data when too many results may be returned Herewe customize the pagination by setting the page size to be an application parameterpostsPerPage Second it does sorting according to the user request And finally it feedsthe paginated and sorted data to widgets or view code for presentation

28 3 Post Management

After we finish with actionIndex() we modify the index view as follows Our change ismainly about adding the h1 header when the user specifies to display posts with a tag

ltphp if(empty($ GET[rsquotagrsquo])) gt

lth1gtPosts Tagged with ltigtltphp echo CHtmlencode($ GET[rsquotagrsquo]) gtltigtlth1gt

ltphp endif gt

ltphp $this-gtwidget(rsquoziiwidgetsCListViewrsquo array(

rsquodataProviderrsquo=gt$dataProvider

rsquoitemViewrsquo=gtrsquo viewrsquo

rsquotemplatersquo=gtitemsnpager)) gt

Note that in the above we use [CListView] to display the post list This widget requires apartial view to display the detail of each individual post Here we specify the partial viewto be view which means the file wwwrootblogprotectedviewspost viewphp In thisview script we can acccess the post instance being displayed via a local variable named$data

34 Managing Posts

Managing posts mainly refers to listing posts in an administrative view that allows us tosee posts with all statuses updating them and deleting them They are accomplished bythe admin operation and the delete operation respectively The code generated by yiic

does not need much modification Below we mainly explain how these two operations areimplemented

341 Listing Posts in Tabular View

The admin operation shows posts with all statuses in a tabular view The view supportssorting and pagination The following is the actionAdmin() method in PostController

public function actionAdmin()

$dataProvider=new CActiveDataProvider(rsquoPostrsquo array(

rsquosortrsquo=gtarray(

rsquodefaultOrderrsquo=gtrsquostatus update time DESCrsquo

)

))

$this-gtrender(rsquoadminrsquoarray(

rsquodataProviderrsquo=gt$dataProvider

))

34 Managing Posts 29

The above code is very similar to that in actionIndex() The main difference is in thequery criteria Here we do not specify any particular query criteria since we want to seeall posts We do customize the sort property of the data provider so that by default theposts are shown in the order of their statuses and update time

Below is the code for the admin view

lth1gtManage Postslth1gt

ltphp $this-gtwidget(rsquoziiwidgetsgridCGridViewrsquo array(

rsquodataProviderrsquo=gt$dataProvider

rsquocolumnsrsquo=gtarray(

array(

rsquonamersquo=gtrsquotitlersquo

rsquotypersquo=gtrsquorawrsquo

rsquovaluersquo=gtrsquoCHtmllink(CHtmlencode($data-gttitle) $data-gturl)rsquo

)

array(

rsquonamersquo=gtrsquostatusrsquo

rsquovaluersquo=gtrsquoLookupitem(PostStatus$data-gtstatus)rsquo

)

rsquocreate timedatetimersquo

rsquoupdate timedatetimersquo

array(

rsquoclassrsquo=gtrsquoCButtonColumnrsquo

)

)

)) gt

We use [CGridView] to display the posts It allows us to sort by a column and paginatethrough the posts if there are too many to be displayed in a single page Our change ismainly about how to display each column For example for the title column we specifythat it should be displayed as a hyperlink that points to the detailed view of the post

Tip When displaying text we call CHtmlencode() to encode HTML entities init This prevents from cross-site scripting attack

342 Deleting Posts

In the admin data grid there is a delete button in each row Clicking on the button shoulddelete the corresponding post Internally this triggers the delete action implemented asfollows

30 3 Post Management

public function actionDelete()

if(Yiiapp()-gtrequest-gtisPostRequest)

we only allow deletion via POST request

$this-gtloadModel()-gtdelete()

if(isset($ POST[rsquoajaxrsquo]))

$this-gtredirect(array(rsquoindexrsquo))

else

throw new CHttpException(400rsquoInvalid request Please do not repeat this request againrsquo)

The above code is the one generated by the yiic tool without any change We wouldlike to explain a little bit more about the checking on $ POST[rsquoajaxrsquo] The [CGridView]widget has a very nice feature that its sorting pagination and deletion operations are alldone in AJAX mode by default That means the whole page does not get reloaded if anyof the above operations is performed However it is also possible that the widget runs innon-AJAX mode (by setting its ajaxUpdate property to be false or disabling JavaScript onthe client side) It is necessary for the delete action to differentiate these two scenarios ifthe delete request is made via AJAX we should not redirect the user browser otherwisewe should

Deleting a post should also cause the deletion of all comments for that post In additionwe should also update the tbl tag table regarding the tags for the deleted post Both ofthese tasks can be achieved by writing an afterDelete method in the Post model class asfollows

protected function afterDelete()

parentafterDelete()

Commentmodel()-gtdeleteAll(rsquopost id=rsquo$this-gtid)

Tagmodel()-gtupdateFrequency($this-gttags rsquorsquo)

The above code is very straightforward it first deletes all those comments whose post id

is the same as the ID of the deleted post it then updates the tbl tag table for the tags

of the deleted post

34 Managing Posts 31

Tip We have to explicitly delete all comments for the deleted post here becauseSQLite does not really support foreign key constraints In a DBMS that supportsthis constraint (such as MySQL PostgreSQL) the foreign key constraint can be setup such that the DBMS automatically deletes the related comments if the post isdeleted In that case we no longer this explicit deletion call in our code

32 3 Post Management

Chapter 4

Comment Management

41 Customizing Comment Model

For the Comment model we mainly need to customize the rules() and attributeLabels()

methods The attributeLabels() method returns a mapping between attribute namesand attribute labels We do not need to touch relations() since the code generated bythe yiic tool is good enough

411 Customizing rules() Method

We first customize the validation rules generated by the yiic tool The following rules areused for comments

public function rules()

return array(

array(rsquocontent author emailrsquo rsquorequiredrsquo)

array(rsquoauthor email urlrsquo rsquolengthrsquo rsquomaxrsquo=gt128)

array(rsquoemailrsquorsquoemailrsquo)

array(rsquourlrsquorsquourlrsquo)

)

In the above we specify that the author email and content attributes are required thelength of author email and url cannot exceed 128 the email attribute must be a validemail address and the url attribute must be a valid URL

412 Customizing attributeLabels() Method

We then customize the attributeLabels() method to declare the label display for eachmodel attribute This method returns an array consisting of name-label pairs When wecall CHtmlactiveLabel() to display an attribute label

34 4 Comment Management

public function attributeLabels()

return array(

rsquoidrsquo =gt rsquoIdrsquo

rsquocontentrsquo =gt rsquoCommentrsquo

rsquostatusrsquo =gt rsquoStatusrsquo

rsquocreate timersquo =gt rsquoCreate Timersquo

rsquoauthorrsquo =gt rsquoNamersquo

rsquoemailrsquo =gt rsquoEmailrsquo

rsquourlrsquo =gt rsquoWebsitersquo

rsquopost idrsquo =gt rsquoPostrsquo

)

Tip If the label for an attribute is not declared in attributeLabels() an algo-rithm will be used to generate an appropriate label For example a label VerifyCode will be generated for attributes verify code or verifyCode

413 Customizing Saving Process

Because we want to record the creation time of a comment we override the beforeSave()

method of Comment like we do for the Post model

protected function beforeSave()

if(parentbeforeSave())

if($this-gtisNewRecord)

$this-gtcreate time=time()

return true

else

return false

42 Creating and Displaying Comments

In this section we implement the comment display and creation features

421 Displaying Comments

Instead of displaying and creating comments on individual pages we use the post detailpage (generated by the view action of PostController) Below the post content display

42 Creating and Displaying Comments 35

we display first a list of comments belonging to that post and then a comment creationform

In order to display comments on the post detail page we modify the view script wwwrootblogprotectedviewspostviewphp as follows

ltdiv id=commentsgt

ltphp if($model-gtcommentCountgt=1) gt

lth3gt

ltphp echo $model-gtcommentCount rsquocomment(s)rsquo gt

lth3gt

ltphp $this-gtrenderPartial(rsquo commentsrsquoarray(

rsquopostrsquo=gt$model

rsquocommentsrsquo=gt$model-gtcomments

)) gt

ltphp endif gt

ltdivgt

In the above we call renderPartial() to render a partial view named comments to displaythe list of comments belonging to the current post Note that in the view we use theexpression $model-gtcomments to retrieve the comments for the post This is valid becausewe have declared a comments relation in the Post class Evaluating this expression wouldtrigger an implicit JOIN database query to bring back the proper comments This featureis known as lazy relational query

The partial view comments is not very interesting It mainly goes through every commentand displays the detail of it Interested readers may refer to wwwrootyiidemosblog

protectedpost commentsphp

422 Creating Comments

To handle comment creation we first modify the actionView() method of PostController

as follows

public function actionView()

$post=$this-gtloadModel()

$comment=$this-gtnewComment($post)

$this-gtrender(rsquoviewrsquoarray(

rsquomodelrsquo=gt$post

rsquocommentrsquo=gt$comment

))

36 4 Comment Management

protected function newComment($post)

$comment=new Comment

if(isset($ POST[rsquoCommentrsquo]))

$comment-gtattributes=$ POST[rsquoCommentrsquo]

if($post-gtaddComment($comment))

if($comment-gtstatus==CommentSTATUS PENDING)

Yiiapp()-gtuser-gtsetFlash(rsquocommentSubmittedrsquorsquoThank yoursquo)

$this-gtrefresh()

return $comment

In the above we call the newComment() method before we render view In the newComment()

method we generate a Comment instance and check if the comment form is submitted If sowe try to add the comment for the post by calling $post-gtaddComment($comment) If it goesthrough we refresh the post detail page In case the comment needs to be approved we willshow a flash message to indicate this decision A flash message is usually a confirmationmessage displayed to end users If the user clicks on the refresh button of his browser themessage will disappear

We also need to modify wwwrootblogprotectedviewspostviewphp furthermore

ltdiv id=commentsgt

lth3gtLeave a Commentlth3gt

ltphp if(Yiiapp()-gtuser-gthasFlash(rsquocommentSubmittedrsquo)) gt

ltdiv class=successgt

ltphp echo Yiiapp()-gtuser-gtgetFlash(rsquocommentSubmittedrsquo) gt

ltdivgt

ltphp else gt

ltphp $this-gtrenderPartial(rsquocomment formrsquoarray(

rsquomodelrsquo=gt$comment

)) gt

ltphp endif gt

ltdivgtlt-- comments --gt

43 Managing Comments 37

In the above code we display the flash message if it is available If not we displaythe comment input form by rendering the partial view wwwrootblogprotectedviews

comment formphp

43 Managing Comments

Comment management includes updating deleting and approving comments These op-erations are implemented as actions in the CommentController class

431 Updating and Deleting Comments

The code generated by yiic for updating and deleting comments remains largely un-changed

432 Approving Comments

When comments are newly created they are in pending approval status and need to beapproved in order to be visible to guest users Approving a comment is mainly aboutchanging the status column of the comment

We create an actionApprove() method in CommentController as follows

public function actionApprove()

if(Yiiapp()-gtrequest-gtisPostRequest)

$comment=$this-gtloadModel()

$comment-gtapprove()

$this-gtredirect(array(rsquoindexrsquo))

else

throw new CHttpException(400rsquoInvalid requestrsquo)

In the above when the approve action is invoked via a POST request we call the approve()

method defined in the Comment model to change the status We then redirect the userbrowser to the page displaying the post that this comment belongs to

We also modify the actionIndex() method of Comment to show all comments We wouldlike to see comments pending approved to show up first

public function actionIndex()

38 4 Comment Management

$dataProvider=new CActiveDataProvider(rsquoCommentrsquo array(

rsquocriteriarsquo=gtarray(

rsquowithrsquo=gtrsquopostrsquo

rsquoorderrsquo=gtrsquotstatus tcreate time DESCrsquo

)

))

$this-gtrender(rsquoindexrsquoarray(

rsquodataProviderrsquo=gt$dataProvider

))

Notice that in the above code because both tbl post and tbl comment have columns statusand create time we need to disambiguate the corresponding column reference by prefixingthem with table alias names As described in the guide the alias for the primary table ina relational query is always t Therefore we are prefixing t to the status and create time

columns in the above code

Like the post index view the index view for CommentController uses [CListView] to displaythe comment list which in turn uses the partial view wwwrootblogprotectedviews

comment viewphp to display the detail of each individual comment We will not go intodetails here Interested readers may refer to the corresponding file in the blog demowwwrootyiidemosblogprotectedviewscomment viewphp

Chapter 5

Portlets

51 Customizing Page Layout

Features like rdquothe most recent commentsrdquo rdquotag cloudrdquo are better to be implemented inportlets A portlet is a pluggable user interface component that renders a fragment ofHTML code In this section we describe how to set up the portlet architecture for ourblog application

Before we go into details about developing portlets we first need to adjust our page layoutso that the portlets can be placed in a side bar

A page layout determines how to share common sections (eg header footer) amongdifferent pages and how to embed contents that are specific for different pages In Yiia page layout is represented as a view under the protectedviewslayouts folder Whenwe call the render() method in a controller the rendering result will be automaticallyembedded into this layout view before it is displayed to end users The default layoutview is named as main which means the wwwrootblogprotectedviewslayoutsmain

php file in our blog application

We could directly modify the main layout to add a side bar However doing so wouldmake some irrelevant pages such as about contact pages to have a side bar which is notalways desirable For this reason we need different layouts for different pages

We thus create two different layouts column1 and column2 The former displays the pagein a single column while the latter displays two columns in the page one of which is theside bar We will use column2 layout for the post and comment pages and column1 forthe rest of the pages Because both column1 and column2 still share the same header andfooter sections we let them to share the main layout

Based on the above description we create the column1 layout as follows

ltphp $this-gtbeginContent(rsquolayoutsmainrsquo) gt

ltdiv class=containergt

40 5 Portlets

ltdiv id=contentgt

ltphp echo $content gt

ltdivgtlt-- content --gt

ltdivgt

ltphp $this-gtendContent() gt

and the column2 layout as follows

ltphp $this-gtbeginContent(rsquolayoutsmainrsquo) gt

ltdiv class=containergt

ltdiv class=span-18gt

ltdiv id=contentgt

ltphp echo $content gt

ltdivgtlt-- content --gt

ltdivgt

ltdiv class=span-6 lastgt

ltdiv id=sidebargt

portlets to be placed here

ltdivgtlt-- sidebar --gt

ltdivgt

ltdivgt

ltphp $this-gtendContent() gt

As we can see both column1 and column2 are enclosed in a pair of beginContent andendContent calls They essentially use the specified view layoutsmain to decorate theenclosed content In all these layout views the variable $content refers to the dynamiccontent to be embedded into the layout

Besides customizing the layout view file we also need to adjust the CSS file wwwroot

blogcssmaincss so that the overall appearance would look like what we see in the blogdemo We will not go into details here

Info The skeleton application generated by the yiic tool uses the BluePrint CSSframework It provides a nice font reset and a robust grid system For more detailson how to use this framework please refer to its documentation

After creating the new layouts we need to set the layout property of controllers to makeuse of them So far we have three controllers SiteController PostController andCommentController We set SiteController to use column1 as the default layout whilethe rest two of the controllers use column2 for example

class PostController extends Controller

52 Creating User Menu Portlet 41

public $layout=rsquocolumn2rsquo

Tip If some view of the controller does not use the same layout as declaredby the layout property it may customize it in the view by a simple assignment$this-gtlayout=rsquonewlayoutrsquo

52 Creating User Menu Portlet

Based on the requirements analysis we need three different portlets the rdquouser menurdquoportlet the rdquotag cloudrdquo portlet and the rdquorecent commentsrdquo portlet We will develop theseportlets by extending the [CPortlet] widget provided by Yii

In this section we will develop our first concrete portlet - the user menu portlet whichdisplays a list of menu items that are only available to authenticated users The menucontains four items

bull Approve Comments a hyperlink that leads to a list of comments pending approval

bull Create New Post a hyperlink that leads to the post creation page

bull Manage Posts a hyperlink that leads to the post management page

bull Logout a link button that would log out the current user

521 Creating UserMenu Class

We create the UserMenu class to represent the logic part of the user menu portlet Theclass is saved in the file wwwrootblogprotectedcomponentsUserMenuphp which has thefollowing content

Yiiimport(rsquoziiwidgetsCPortletrsquo)

class UserMenu extends CPortlet

public function init()

$this-gttitle=CHtmlencode(Yiiapp()-gtuser-gtname)

parentinit()

42 5 Portlets

protected function renderContent()

$this-gtrender(rsquouserMenursquo)

The UserMenu class extends from the CPortlet class that we created previously It overridesboth the init() method and the renderContent() method of CPortlet The former setsthe portlet title to be the name of the current user the latter generates the portlet bodycontent by rendering a view named userMenu

Tip Notice that we have to explicitly include the CPortlet class by calling Yii

import() before we refer to it the first time This is because CPortlet is part of thezii project ndash the official extension library for Yii For performance considerationclasses in this project are not listed as core classes Therefore we have to import itbefore we use it the first time

522 Creating userMenu View

Next we create the userMenu view which is saved in the file wwwrootblogprotected

componentsviewsuserMenuphp

ltulgt

ltligtltphp echo CHtmllink(rsquoCreate New Postrsquoarray(rsquopostcreatersquo)) gtltligt

ltligtltphp echo CHtmllink(rsquoManage Postsrsquoarray(rsquopostadminrsquo)) gtltligt

ltligtltphp echo CHtmllink(rsquoApprove Commentsrsquoarray(rsquocommentindexrsquo))

rsquo (rsquo Commentmodel()-gtpendingCommentCount rsquo)rsquo gtltligt

ltligtltphp echo CHtmllink(rsquoLogoutrsquoarray(rsquositelogoutrsquo)) gtltligt

ltulgt

Info By default view files for a widget should be placed under the views sub-directory of the directory containing the widget class file The file name must bethe same as the view name

523 Using UserMenu Portlet

It is time for us to make use of our newly completed UserMenu portlet We modify thelayout view file wwwrootblogprotectedviewslayoutscolumn2php as follows

53 Creating Tag Cloud Portlet 43

ltdiv id=sidebargt

ltphp if(Yiiapp()-gtuser-gtisGuest) $this-gtwidget(rsquoUserMenursquo) gt

ltdivgt

In the above we call the widget() method to generate and execute an instance of theUserMenu class Because the portlet should only be displayed to authenticated users weonly call widget() when the isGuest property of the current user is false (meaning theuser is authenticated)

524 Testing UserMenu Portlet

Letrsquos test what we have so far

1 Open a browser window and enter the URL httpwwwexamplecomblogindex

php Verify that there is nothing displayed in the side bar section of the page

2 Click on the Login hyperlink and fill out the login form to login If successful verifythat the UserMenu portlet appears in the side bar and the portlet has the usernameas its title

3 Click on the rsquoLogoutrsquo hyperlink in the UserMenu portlet Verify that the logout actionis successful and the UserMenu portlet disappears

525 Summary

What we have created is a portlet that is highly reusable We can easily reuse it in adifferent project with little or no modification Moreover the design of this portlet followsclosely the philosophy that logic and presentation should be separated While we did notpoint this out in the previous sections such practice is used nearly everywhere in a typicalYii application

53 Creating Tag Cloud Portlet

Tag cloud displays a list of post tags with visual decorations hinting the popularity of eachindividual tag

531 Creating TagCloud Class

We create the TagCloud class in the file wwwrootblogprotectedcomponentsTagCloud

php The file has the following content

44 5 Portlets

Yiiimport(rsquoziiwidgetsCPortletrsquo)

class TagCloud extends CPortlet

public $title=rsquoTagsrsquo

public $maxTags=20

protected function renderContent()

$tags=Tagmodel()-gtfindTagWeights($this-gtmaxTags)

foreach($tags as $tag=gt$weight)

$link=CHtmllink(CHtmlencode($tag) array(rsquopostindexrsquorsquotagrsquo=gt$tag))

echo CHtmltag(rsquospanrsquo array(

rsquoclassrsquo=gtrsquotagrsquo

rsquostylersquo=gtfont-size$weightpt) $link)n

Unlike the UserMenu portlet the TagCloud portlet does not use a view Instead its presen-tation is done in the renderContent() method This is because the presentation does notcontain much HTML tags

We display each tag as a hyperlink to the post index page with the corresponding tagparameter The font size of each tag link is adjusted according to their relative weightamong other tags If a tag has higher frequency value than the other it will have a biggerfont size

532 Using TagCloud Portlet

Usage of the TagCloud portlet is very simple We modify the layout file wwwrootblog

protectedviewslayoutsmainphp as follows

ltdiv id=sidebargt

ltphp if(Yiiapp()-gtuser-gtisGuest) $this-gtwidget(rsquoUserMenursquo) gt

ltphp $this-gtwidget(rsquoTagCloudrsquo array(

rsquomaxTagsrsquo=gtYiiapp()-gtparams[rsquotagCloudCountrsquo]

)) gt

ltdivgt

54 Creating Recent Comments Portlet 45

54 Creating Recent Comments Portlet

In this section we create the last portlet that displays a list of comments recently pub-lished

541 Creating RecentComments Class

We create the RecentComments class in the file wwwrootblogprotectedcomponentsRecentCommentsphp The file has the following content

Yiiimport(rsquoziiwidgetsCPortletrsquo)

class RecentComments extends CPortlet

public $title=rsquoRecent Commentsrsquo

public $maxComments=10

public function getRecentComments()

return Commentmodel()-gtfindRecentComments($this-gtmaxComments)

protected function renderContent()

$this-gtrender(rsquorecentCommentsrsquo)

In the above we invoke the findRecentComments method which is defined in the Comment

class as follows

class Comment extends CActiveRecord

public function findRecentComments($limit=10)

return $this-gtwith(rsquopostrsquo)-gtfindAll(array(

rsquoconditionrsquo=gtrsquotstatus=rsquoselfSTATUS APPROVED

rsquoorderrsquo=gtrsquotcreate time DESCrsquo

rsquolimitrsquo=gt$limit

))

46 5 Portlets

542 Creating recentComments View

The recentComments view is saved in the file wwwrootblogprotectedcomponentsviews

recentCommentsphp It simply displays every comment returned by the RecentComments

getRecentComments() method

543 Using RecentComments Portlet

We modify the layout file wwwrootblogprotectedviewslayoutsmainphp to embedthis last portlet

ltdiv id=sidebargt

ltphp if(Yiiapp()-gtuser-gtisGuest) $this-gtwidget(rsquoUserMenursquo) gt

ltphp $this-gtwidget(rsquoTagCloudrsquo array(

rsquomaxTagsrsquo=gtYiiapp()-gtparams[rsquotagCloudCountrsquo]

)) gt

ltphp $this-gtwidget(rsquoRecentCommentsrsquo array(

rsquomaxCommentsrsquo=gtYiiapp()-gtparams[rsquorecentCommentCountrsquo]

)) gt

ltdivgt

Chapter 6

Final Work

61 Beautifying URLs

The URLs linking various pages of our blog application currently look ugly For examplethe URL for the page showing a post looks like the following

indexphpr=postshowampid=1

In this section we describe how to beautify these URLs and make them SEO-friendlyOur goal is to be able to use the following URLs in the application

1 indexphppostsyii leads to the page showing a list of posts with tag yii

2 indexphppost2A+Test+Post leads to the page showing the detail of the postwith ID 2 whose title is A Test Post

3 indexphppostupdate1 leads to the page that allows updating the post with ID1

Note that in the second URL format we include the post title in the URL This is mainlyto make the URL SEO friendly It is said that search engines may also respect the wordsfound in a URL when it is being indexed

To achieve our goal we modify the application configuration as follows

return array(

rsquocomponentsrsquo=gtarray(

rsquourlManagerrsquo=gtarray(

rsquourlFormatrsquo=gtrsquopathrsquo

rsquorulesrsquo=gtarray(

48 6 Final Work

rsquopostltidd+gtlttitlegtrsquo=gtrsquopostviewrsquorsquopostslttaggtrsquo=gtrsquopostindexrsquo

)

)

)

)

In the above we configure the urlManager component by setting its urlFormat propertyto be path and adding a set of rules

The rules are used by urlManager to parse and create the URLs in the desired formatFor example the first rule says that if a URL indexphppostsyii is requested theurlManager component should be responsible to dispatch the request to the route post

index and generate a tag GET parameter with the value yii On the other hand whencreating a URL with the route postindex and parameter tag the urlManager componentwill also use this rule to generate the desired URL indexphppostsyii For this reasonwe say that urlManager is a two-way URL manager

The urlManager component can further beautify our URLs such as hiding indexphp inthe URLs appending suffix like html to the URLs We can obtain these features easilyby configuring various properties of urlManager in the application configuration For moredetails please refer to the Guide

62 Logging Errors

A production Web application often needs sophisticated logging for various events In ourblog application we would like to log the errors occurring when it is being used Sucherrors could be programming mistakes or usersrsquo misuse of the system Logging these errorswill help us to improve the blog application

We enable the error logging by modifying the application configuration as follows

return array(

rsquopreloadrsquo=gtarray(rsquologrsquo)

rsquocomponentsrsquo=gtarray(

rsquologrsquo=gtarray(

rsquoclassrsquo=gtrsquoCLogRouterrsquo

rsquoroutesrsquo=gtarray(

array(

rsquoclassrsquo=gtrsquoCFileLogRoutersquo

rsquolevelsrsquo=gtrsquoerror warningrsquo

63 Final Tune-up and Deployment 49

)

)

)

)

)

With the above configuration if an error or warning occurs detailed information will belogged and saved in a file located under the directory wwwrootblogprotectedruntime

The log component offers more advanced features such as sending log messages to a listof email addresses displaying log messages in JavaScript console window etc For moredetails please refer to the Guide

63 Final Tune-up and Deployment

We are close to finish our blog application Before deployment we would like to do sometune-ups

631 Changing Home Page

We change to use the post list page as the home page We modify the application config-uration as follows

return array(

rsquodefaultControllerrsquo=gtrsquopostrsquo

)

Tip Because PostController already declares list to be its default action whenwe access the home page of the application we will see the result generated by thelist action of the post controller

632 Enabling Schema Caching

Because ActiveRecord relies on the metadata about tables to determine the column infor-mation it takes time to read the metadata and analyze it This may not be a problemduring development stage but for an application running in production mode it is a totalwaste of time if the database schema does not change Therefore we should enable theschema caching by modifying the application configuration as follows

50 6 Final Work

return array(

rsquocomponentsrsquo=gtarray(

rsquocachersquo=gtarray(

rsquoclassrsquo=gtrsquoCDbCachersquo

)

rsquodbrsquo=gtarray(

rsquoclassrsquo=gtrsquosystemdbCDbConnectionrsquo

rsquoconnectionStringrsquo=gtrsquosqlitewwwrootblogprotecteddatablogdbrsquo

rsquoschemaCachingDurationrsquo=gt3600

)

)

)

In the above we first add a cache component which uses a default SQLite database as thecaching storage If our server is equipped with other caching extensions such as APCwe could change to use them as well We also modify the db component by setting itsschemaCachingDuration property to be 3600 which means the parsed database schemadata can remain valid in cache for 3600 seconds

633 Disabling Debugging Mode

We modify the entry script file wwwrootblogindexphp by removing the line definingthe constant YII DEBUG This constant is useful during development stage because it allowsYii to display more debugging information when an error occurs However when theapplication is running in production mode displaying debugging information is not agood idea because it may contain sensitive information such as where the script file islocated and the content in the file etc

634 Deploying the Application

The final deployment process manly involves copying the directory wwwrootblog to thetarget directory The following checklist shows every needed step

1 Install Yii in the target place if it is not available

2 Copy the entire directory wwwrootblog to the target place

3 Edit the entry script file indexphp by pointing the $yii variable to the new Yiibootstrap file

4 Edit the file protectedyiicphp by setting the $yiic variable to be the new Yiiyiicphp file

64 Future Enhancements 51

5 Change the permission of the directories assets and protectedruntime so that theyare writable by the Web server process

64 Future Enhancements

641 Using a Theme

Without writing any code our blog application is already themeable To use a themewe mainly need to develop the theme by writing customized view files in the theme Forexample to use a theme named classic that uses a different page layout we would createa layout view file wwwrootblogthemesclassicviewslayoutsmainphp We also needto change the application configuration to indicate our choice of the classic theme

return array(

rsquothemersquo=gtrsquoclassicrsquo

)

642 Internationalization

We may also internationalize our blog application so that its pages can be displayed indifferent languages This mainly involves efforts in two aspects

First we may create view files in different languages For example for the list pageof PostController we can create a view file wwwrootblogprotectedviewspostzh cn

listphp When the application is configured to use simplified Chinese (the language codeis zh cn) Yii will automatically use this new view file instead of the original one

Second we may create message translations for those messages generated by code Themessage translations should be saved as files under the directory wwwrootblogprotected

messages We also need to modify the code where we use text strings by enclosing themin the method call Yiit()

For more details about internationalization please refer to the Guide

643 Improving Performance with Cache

While the Yii framework itself is very efficient it is not necessarily true that an applicationwritten in Yii efficient There are several places in our blog application that we can improvethe performance For example the tag clould portlet could be one of the performancebottlenecks because it involves complex database query and PHP logic

52 6 Final Work

We can make use of the sophisticated caching feature provided by Yii to improve theperformance One of the most useful components in Yii is COutputCache which caches afragment of page display so that the underlying code generating the fragment does not needto be executed for every request For example in the layout file wwwrootblogprotected

viewslayoutsmainphp we can enclose the tag cloud portlet with COutputCache

ltphp if($this-gtbeginCache(rsquotagCloudrsquo array(rsquodurationrsquo=gt3600))) gt

ltphp $this-gtwidget(rsquoTagCloudrsquo array(

rsquomaxTagsrsquo=gtYiiapp()-gtparams[rsquotagCloudCountrsquo]

)) gt

ltphp $this-gtendCache() gt

With the above code the tag cloud display will be served from cache instead of beinggenerated on-the-fly for every request The cached content will remain valid in cache for3600 seconds

644 Adding New Features

Our blog application only has very basic functionalities To become a complete blogsystem more features are needed for example calendar portlet email notifications postcategorization archived post portlet and so on We will leave the implementation of thesefeatures to interested readers

  • Contents
  • License
  • Getting Started
    • Building a Blog System using Yii
    • Testdriving with Yii
      • Installing Yii
      • Creating Skeleton Application
      • Application Workflow
        • Requirements Analysis
        • Overall Design
          • Initial Prototyping
            • Setting Up Database
              • Creating Database
              • Establishing Database Connection
                • Scaffolding
                • Authenticating User
                • Summary
                  • Post Management
                    • Customizing Post Model
                      • Customizing rules() Method
                      • Customizing relations() Method
                      • Representing Status in Text
                        • Creating and Updating Posts
                          • Customizing Access Control
                          • Customizing create and update Operations
                            • Displaying Posts
                              • Customizing view Operation
                              • Customizing index Operation
                                • Managing Posts
                                  • Listing Posts in Tabular View
                                  • Deleting Posts
                                      • Comment Management
                                        • Customizing Comment Model
                                          • Customizing rules() Method
                                          • Customizing attributeLabels() Method
                                          • Customizing Saving Process
                                            • Creating and Displaying Comments
                                              • Displaying Comments
                                              • Creating Comments
                                                • Managing Comments
                                                  • Updating and Deleting Comments
                                                  • Approving Comments
                                                      • Portlets
                                                        • Customizing Page Layout
                                                        • Creating User Menu Portlet
                                                          • Creating UserMenu Class
                                                          • Creating userMenu View
                                                          • Using UserMenu Portlet
                                                          • Testing UserMenu Portlet
                                                          • Summary
                                                            • Creating Tag Cloud Portlet
                                                              • Creating TagCloud Class
                                                              • Using TagCloud Portlet
                                                                • Creating Recent Comments Portlet
                                                                  • Creating RecentComments Class
                                                                  • Creating recentComments View
                                                                  • Using RecentComments Portlet
                                                                      • Final Work
                                                                        • Beautifying URLs
                                                                        • Logging Errors
                                                                        • Final Tune-up and Deployment
                                                                          • Changing Home Page
                                                                          • Enabling Schema Caching
                                                                          • Disabling Debugging Mode
                                                                          • Deploying the Application
                                                                            • Future Enhancements
                                                                              • Using a Theme
                                                                              • Internationalization
                                                                              • Improving Performance with Cache
                                                                              • Adding New Features
Page 14: Yii Blog 1.1.0 Demo Tutor

14 Overall Design 7

We divide the development of our blog application into the following milestones

bull Milestone 1 creating a prototype of the blog system It should consist of most ofthe required functionalities

bull Milestone 2 completing post management It includes creating listing showingupdating and deleting posts

bull Milestone 3 completing comment management It includes creating listing ap-proving updating and deleting post comments

bull Milestone 4 implementing portlets It includes user menu login tag cloud andrecent comments portlets

bull Milestone 5 final tune-up and deployment

8 1 Getting Started

Chapter 2

Initial Prototyping

21 Setting Up Database

Having created a skeleton application and finished the database design in this section wewill create the blog database and establish the connection to it in the skeleton application

211 Creating Database

We choose to create a SQLite database Because the database support in Yii is built on topof PDO we can easily switch to use a different type of DBMS (eg MySQL PostgreSQL)without the need to change our application code

We create the database file blogdb under the directory wwwrootblogprotecteddataNote that both the directory and the database file have to be writable by the Web serverprocess as required by SQLite We may simply copy the database file from the blogdemo in our Yii installation which is located at wwwrootyiidemosblogprotecteddatablogdb We may also generate the database by executing the SQL statements in the filewwwrootyiidemosblogprotecteddataschemasqlitesql

Tip To execute SQL statements we may use the sqlite3 command line tool thatcan be found in the SQLite official website

212 Establishing Database Connection

To use the blog database in the skeleton application we created we need to modify itsapplication configuration which is stored in the PHP script wwwrootblogprotected

configmainphp The script returns an associative array consisting of name-value pairseach of which is used to initialize a writable property of the application instance

We configure the db component as follows

10 2 Initial Prototyping

return array(

rsquocomponentsrsquo=gtarray(

rsquodbrsquo=gtarray(

rsquoconnectionStringrsquo=gtrsquosqlitewwwrootblogprotecteddatablogdbrsquo

rsquotablePrefixrsquo=gtrsquotbl rsquo

)

)

)

The above configuration says that we have a db application component whose connectionStringproperty should be initialized as sqlitewwwrootblogprotecteddatablogdb and whosetablePrefix property should be tbl

With this configuration we can access the DB connection object using Yiiapp()-gtdb

at any place in our code Note that Yiiapp() returns the application instance that wecreate in the entry script If you are interested in possible methods and properties that theDB connection has you may refer to its class reference However in most cases we are notgoing to use this DB connection directly Instead we will use the so-called ActiveRecordto access the database

We would like to explain a bit more about the tablePrefix property that we set in theconfiguration This tells the db connection that it should respect the fact we are usingtbl as the prefix to our database table names In particular if in a SQL statement thereis a token enclosed within double curly brackets (eg post then the db connectionshould translate it into a name with the table prefix (eg tbl post) before sending it toDBMS for execution This feature is especially useful if in future we need to modify thetable name prefix without touching our source code For example if we are developing ageneric content management system (CMS) we may exploit this feature so that when itis being installed in a new environment we can allow users to choose a table prefix theylike

22 Scaffolding 11

Tip If you want to use MySQL instead of SQLite to store data you may cre-ate a MySQL database named blog using the SQL statements in wwwrootyii

demosblogprotecteddataschemamysqlsql Then modify the applicationconfiguration as follows

return array(

rsquocomponentsrsquo=gtarray(

rsquodbrsquo=gtarray(

rsquoconnectionStringrsquo =gt rsquomysqlhost=localhostdbname=blogrsquo

rsquoemulatePreparersquo =gt true

rsquousernamersquo =gt rsquorootrsquo

rsquopasswordrsquo =gt rsquorsquo

rsquocharsetrsquo =gt rsquoutf8rsquo

rsquotablePrefixrsquo =gt rsquotbl rsquo

)

)

)

22 Scaffolding

Create read update and delete (CRUD) are the four basic operations of data objects in anapplication Because the task of implementing the CRUD operations is so common whendeveloping Web applications Yii provides a tool to automate this process (also known asscaffolding) In this section we will describe how to use the tool to implement CRUD forposts and comments

Open a command window and run the following commands

wwwrootyiiframeworkyiic shell wwwrootblogindexphp

Yii Interactive Tool v11

Please type rsquohelprsquo for help Type rsquoexitrsquo to quit

gtgt model

gtgt crud Post

gtgt crud Comment

gtgt exit

12 2 Initial Prototyping

Info Some PHP installations may use a different phpini file for command line(CLI) PHP parser As a result when running the above yiic commands youmay encounter errors like rdquoYiiBaseinclude(PDOphp) failed to open streamrdquoor rdquocould not find driverrdquo Please double check your CLI PHP configuration byexecuting the following command

php -r phpinfo()

The result of the above command will show which phpini file is being used andwhich extensions are loaded If a wrong phpini file is used you may use thefollowing command to explicitly specify the correct phpini to use

php -c phpini wwwrootyiiframeworkyiicphp shell wwwrootblogindexphp

The commands above accomplish two tasks First the model command generates a modelclass file for each table in the blog database Second the crud commands generate thecode needed by the CRUD operations for the Post and Comment models

Tip The command model generates a model class for every table in the databaseSometimes we probably donrsquot want to do so (eg the database contains some irrel-evant tables) In this case we can create model classes one by one For exampleto create the User model we can use the command model User The model com-mand also has some more advanced usages For more details execute help model

command

We can test the generated code by accessing the following URLs

httpwwwexamplecomblogindexphpr=post

httpwwwexamplecomblogindexphpr=comment

Notice that the post and comment features implemented by the generated code are com-pletely independent of each other Also when creating a new post or comment we arerequired to enter information such as author id and create time which in real applica-tion should be set by the program Donrsquot worry We will fix these problems in the nextmilestones For now we should be fairly satisfied as this prototype already contains mostfeatures that we need to implement for the blog application

To prepare for the next milestones letrsquos take a closer look at the files generated by theabove commands All the files are generated under wwwrootblogprotected For conve-nience we group them into model files controller files and view files

22 Scaffolding 13

bull model files

ndash modelsUserphp contains the User class that extends from CActiveRecord andcan be used to access the tbl user database table

ndash modelsPostphp contains the Post class that extends from CActiveRecord andcan be used to access the tbl post database table

ndash modelsTagphp contains the Tag class that extends from CActiveRecord andcan be used to access the tbl tag database table

ndash modelsCommentphp contains the Comment class that extends from CActiveRe-cord and can be used to access the tbl comment database table

ndash modelsLookupphp contains the Lookup class that extends from CActiveRecordand can be used to access the tbl lookup database table

bull controller file

ndash controllersPostControllerphp contains the PostController class which is thecontroller in charge of all CRUD operations about posts

ndash controllersCommentControllerphp contains the CommentController class whichis the controller in charge of all CRUD operations about comments

bull view files

ndash viewspostcreatephp is the view file that shows an HTML form to create anew post

ndash viewspostupdatephp is the view file that shows an HTML form to update anexisting post

ndash viewspostviewphp is the view file that displays the detailed information of apost

ndash viewspostindexphp is the view file that displays a list of posts

ndash viewspostadminphp is the view file that displays posts in a table with ad-ministrative commands

ndash viewspost formphp is the partial view file embedded in viewspostcreate

php and viewspostupdatephp It displays the HTML form for collecting postinformation

ndash viewspost viewphp is the partial view file used by viewspostindexphp Itdisplays the brief view of a single post

ndash a similar set of view files are also generated for comment

14 2 Initial Prototyping

In order to understand better how the above files are used we show in the following theworkflow that occurs in the blog application when displaying a list of posts

1 The user requests the URL httpwwwexamplecomblogindexphpr=posts

2 The entry script is executed by the Web server which creates and initializes anapplication instance to handle the request

3 The application creates an instance of PostController and executes it

4 The PostController instance executes the index action by calling its actionIndex()

method Note that index is the default action if the user does not specify an actionto execute in the URL

5 The actionIndex() method queries database to bring back the list of recent posts

6 The actionIndex() method renders the index view with the post data

23 Authenticating User

Our blog application needs to differentiate between the system owner and guest usersTherefore we need to implement the user authentication feature

As you may have found that the skeleton application already provides user authenticationby checking if the username and password are both demo or admin In this section wewill modify the corresponding code so that the authentication is done against the User

database table

User authentication is performed in a class implementing the IUserIdentity interface Theskeleton application uses the UserIdentity class for this purpose The class is stored inthe file wwwrootblogprotectedcomponentsUserIdentityphp

Tip By convention the name of a class file must be the same as the correspondingclass name suffixed with the extension php Following this convention one canrefer to a class using a path alias For example we can refer to the UserIdentity

class with the alias applicationcomponentsUserIdentity Many APIs in Yiican recognize path aliases (eg YiicreateComponent()) and using path aliasesavoids the necessity of embedding absolute file paths in the code The existence ofthe latter often causes trouble when we deploy an application

We modify the UserIdentity class as follows

23 Authenticating User 15

ltphp

class UserIdentity extends CUserIdentity

private $ id

public function authenticate()

$username=strtolower($this-gtusername)

$user=Usermodel()-gtfind(rsquoLOWER(username)=rsquoarray($username))

if($user===null)

$this-gterrorCode=selfERROR USERNAME INVALID

else if($user-gtvalidatePassword($this-gtpassword))

$this-gterrorCode=selfERROR PASSWORD INVALID

else

$this-gt id=$user-gtid

$this-gtusername=$user-gtusername

$this-gterrorCode=selfERROR NONE

return $this-gterrorCode==selfERROR NONE

public function getId()

return $this-gt id

In the authenticate() method we use the User class to look for a row in the User tablewhose username column is the same as the given username in a case-insensitive mannerRemember that the User class was created using the yiic tool in the prior section Becausethe User class extends from CActiveRecord we can exploit the ActiveRecord feature toaccess the User table in an OOP fashion

In order to check if the user has entered a valid password we invoke the validatePassword

method of the User class We need to modify the file wwwrootblogprotectedmodels

Userphp as follows Note that instead of storing the plain password in the database westore the hash result of the password and a randomly generated salt key When validatingthe user-entered password we should compare the hash results instead

class User extends CActiveRecord

public function validatePassword($password)

return $this-gthashPassword($password$this-gtsalt)===$this-gtpassword

16 2 Initial Prototyping

public function hashPassword($password$salt)

return md5($salt$password)

In the UserIdentity class we also override the getId() method which returns the id valueof the user found in the User table The parent implementation would return the usernameinstead Both the username and id properties will be stored in the user session and maybe accessed via Yiiapp()-gtuser from anywhere in our code

Tip In the UserIdentity class we reference the class CUserIdentity without ex-plicitly including the corresponding class file This is because CUserIdentity is acore class provided by the Yii framework Yii will automatically include the classfile for any core class when it is referenced for the first time

We also do the same with the User class This is because the User class file is placedunder the directory wwwrootblogprotectedmodels which has been added tothe PHP include path according to the following lines found in the applicationconfiguration

return array(

rsquoimportrsquo=gtarray(

rsquoapplicationmodelsrsquo

rsquoapplicationcomponentsrsquo

)

)

The above configuration says that any class whose class file is located under either wwwrootblogprotectedmodels or wwwrootblogprotectedcomponents willbe automatically included when the class is referenced for the first time

The UserIdentity class is mainly used by the LoginForm class to authenticate a user basedon the username and password input collected from the login page The following codefragment shows how UserIdentity is used

$identity=new UserIdentity($username$password)

$identity-gtauthenticate()

switch($identity-gterrorCode)

24 Summary 17

case UserIdentityERROR NONE

Yiiapp()-gtuser-gtlogin($identity)

break

Info People often get confused about identity and the user application componentThe former represents a way of performing authentication while the latter is usedto represent the information related with the current user An application can onlyhave one user component but it can have one or several identity classes dependingon what kind of authentication it supports Once authenticated an identity instancemay pass its state information to the user component so that they are globallyaccessible via user

To test the modified UserIdentity class we can browse the URL httpwwwexample

comblogindexphp and try logging in with the username and password that we store inthe User table If we use the database provided by the blog demo we should be able tologin with username demo and password demo Note that this blog system does not providethe user management feature As a result a user cannot change his account or create anew one through the Web interface The user management feature may be considered asa future enhancement to the blog application

24 Summary

We have completed the milestone 1 Letrsquos summarize what we have done so far

1 We identified the requirements to be fulfilled

2 We installed the Yii framework

3 We created a skeleton application

4 We designed and created the blog database

5 We modified the application configuration by adding the database connection

6 We generated the code that implements the basic CRUD operations for both postsand comments

7 We modified the authentication method to check against the tbl user table

18 2 Initial Prototyping

For a new project most of the time will be spent in step 1 and 4 for this first milestone

Although the code generated by the yiic tool implements fully functional CRUD oper-ations for a database table it often needs to be modified in practical applications Forthis reason in the next two milestone our job is to customize the generated CRUD codeabout posts and comments so that it reaches our initial requirements

In general we first modify the model class file by adding appropriate validation rules anddeclaring relational objects We then modify the controller action and view code for eachindividual CRUD operation

Chapter 3

Post Management

31 Customizing Post Model

The Post model class generated by the yiic tool mainly needs to be modified in two places

bull the rules() method specifies the validation rules for the model attributes

bull the relations() method specifies the related objects

Info A model consists of a list of attributes each associated with a column in thecorresponding database table Attributes can be declared explicitly as class membervariables or implicitly without any declaration

311 Customizing rules() Method

We first specify the validation rules which ensure the attribute values entered by usersare correct before they are saved to the database For example the status attribute ofPost should be an integer 1 2 or 3 The yiic tool also generates validation rules for eachmodel However these rules are based on the table column information and may not beappropriate

Based on the requirement analysis we modify the rules() method as follows

public function rules()

return array(

array(rsquotitle content statusrsquo rsquorequiredrsquo)

array(rsquotitlersquo rsquolengthrsquo rsquomaxrsquo=gt128)

array(rsquostatusrsquo rsquoinrsquo rsquorangersquo=gtarray(123))

array(rsquotagsrsquo rsquomatchrsquo rsquopatternrsquo=gtrsquo^[ws]+$rsquorsquomessagersquo=gtrsquoTags can only contain word charactersrsquo)

20 3 Post Management

array(rsquotagsrsquo rsquonormalizeTagsrsquo)

)

In the above we specify that the title content and status attributes are required thelength of title should not exceed 128 the status attribute value should be 1 (draft) 2(published) or 3 (archived) and the tags attribute should only contain word charactersand commas In addition we use normalizeTags to normalize the user-entered tags so thatthe tags are unique and properly separated with commas

The validators such as required length in and match are all built-in validators providedby Yii The normalizeTags validator is a method-based validator that we need to definein the Post class For more information about how to specify validation rules please referto the Guide

public function normalizeTags($attribute$params)

$this-gttags=Tagarray2string(array unique(Tagstring2array($this-gttags)))

The rules declared in this method are executed one by one when we call the validate() orsave() method of the model instance

Note It is very important to remember that attributes appearing in rules() mustbe those to be entered by end users Other attributes such as id and create time

in the Post model which are set by our code or database should not be in rules()For more details please refer to Securing Attribute Assignments

After making these changes we can visit the post creation page again to verify that thenew validation rules are taking effect

312 Customizing relations() Method

Lastly we customize the relations() method to specify the related objects of a post Bydeclaring these related objects in relations() we can exploit the powerful RelationalActiveRecord (RAR) feature to access the related object information of a post such asits author and comments without the need to write complex SQL JOIN statements

We customize the relations() method as follows

31 Customizing Post Model 21

public function relations()

return array(

rsquoauthorrsquo =gt array(selfBELONGS TO rsquoUserrsquo rsquoauthor idrsquo)

rsquocommentsrsquo =gt array(selfHAS MANY rsquoCommentrsquo rsquopost idrsquo

rsquoconditionrsquo=gtrsquocommentsstatus=rsquoCommentSTATUS APPROVED

rsquoorderrsquo=gtrsquocommentscreate time DESCrsquo)

rsquocommentCountrsquo =gt array(selfSTAT rsquoCommentrsquo rsquopost idrsquo

rsquoconditionrsquo=gtrsquostatus=rsquoCommentSTATUS APPROVED)

)

The above relations state that

bull A post belongs to an author whose class is User and the relationship is establishedbased on the author id attribute value of the post

bull A post has many comments whose class is Comment and the relationship is establishedbased on the post id attribute value of the comments These comments should besorted according to their creation time and the comments must be approved

bull The commentCount relation is a bit special as it returns back an aggregation resultwhich is about how many comments the post has

With the above relation declaration we can easily access the author and comments of apost like the following

$author=$post-gtauthor

echo $author-gtusername

$comments=$post-gtcomments

foreach($comments as $comment)

echo $comment-gtcontent

For more details about how to declare and use relations please refer to the Guide

313 Representing Status in Text

Because the status of a post is stored as an integer in the database we need to provide atextual representation so that it is more intuitive when being displayed to end users In alarge system the similar requirement is very common

22 3 Post Management

As a generic solution we use the tbl lookup table to store the mapping between integervalues and textual representations that are needed by other data objects We modify theLookup model class as follows to more easily access the textual data in the table

class Lookup extends CActiveRecord

private static $ items=array()

public static function items($type)

if(isset(self$ items[$type]))

selfloadItems($type)

return self$ items[$type]

public static function item($type$code)

if(isset(self$ items[$type]))

selfloadItems($type)

return isset(self$ items[$type][$code]) self$ items[$type][$code] false

private static function loadItems($type)

self$ items[$type]=array()

$models=selfmodel()-gtfindAll(array(

rsquoconditionrsquo=gtrsquotype=typersquo

rsquoparamsrsquo=gtarray(rsquotypersquo=gt$type)

rsquoorderrsquo=gtrsquopositionrsquo

))

foreach($models as $model)

self$ items[$type][$model-gtcode]=$model-gtname

Our new code mainly provides two static methods Lookupitems() and Lookupitem()The former returns a list of strings belonging to the specified data type while the latterreturns a particular string for the given data type and data value

Our blog database is pre-populated with two lookup types PostStatus and CommentStatusThe former refers to the possible post statuses while the latter the comment statuses

In order to make our code easier to read we also declare a set of constants to representthe status integer values We should use these constants through our code when referringto the corresponding status values

32 Creating and Updating Posts 23

class Post extends CActiveRecord

const STATUS DRAFT=0

const STATUS PUBLISHED=1

const STATUS ARCHIVED=2

Therefore we can call Lookupitems(rsquoPostStatusrsquo) to get the list of possible post statuses(text strings indexed by the corresponding integer values) and call Lookupitem(rsquoPostStatusrsquoPostSTATUS PUBLISHED) to get the string representation of the published status

32 Creating and Updating Posts

With the Post model ready we need to fine-tune the actions and views for the controllerPostController In this section we first customize the access control of CRUD operationswe then modify the code implementing the create and update operations

321 Customizing Access Control

The first thing we want to do is to customize the access control because the code generatedby yiic does not fit our needs

We modify the accessRules() method in the file wwwrootblogprotectedcontrollers

PostControllerphp as follows

public function accessRules()

return array(

array(rsquoallowrsquo allow all users to perform rsquolistrsquo and rsquoshowrsquo actions

rsquoactionsrsquo=gtarray(rsquoindexrsquo rsquoviewrsquo)

rsquousersrsquo=gtarray(rsquorsquo)

)

array(rsquoallowrsquo allow authenticated users to perform any action

rsquousersrsquo=gtarray(rsquorsquo)

)

array(rsquodenyrsquo deny all users

rsquousersrsquo=gtarray(rsquorsquo)

)

)

The above rules state that all users can access the index and view actions and authenti-cated users can access any actions including the admin action The user should be denied

24 3 Post Management

access in any other scenario Note that these rules are evaluated in the order they arelisted here The first rule matching the current context makes the access decision Forexample if the current user is the system owner who tries to visit the post creation pagethe second rule will match and it will give the access to the user

322 Customizing create and update Operations

The create and update operations are very similar They both need to display an HTMLform to collect user inputs validate them and save them into database The main differ-ence is that the update operation will pre-populate the form with the existing post datafound in the database For this reason the yiic tool generates a partial view wwwroot

blogprotectedviewspost formphp that is embedded in both the create and update

views to render the needed HTML form

We first change the formphp file so that the HTML form only collects the inputs wewant title content tags and status We use plain text fields to collect inputs for thefirst three attributes and a dropdown list to collect input for status The dropdown listoptions are the text displays of the possible post statuses

ltphp echo CHtmlactiveDropDownList($postrsquostatusrsquoLookupitems(rsquoPostStatusrsquo)) gt

In the above we call Lookupitems(rsquoPostStatusrsquo) to bring back the list of post statuses

We then modify the Post class so that it can automatically set some attributes (eg create

time author id) before a post is saved to the database We override the beforeSave()

method as follows

protected function beforeSave()

if(parentbeforeSave())

if($this-gtisNewRecord)

$this-gtcreate time=$this-gtupdate time=time()

$this-gtauthor id=Yiiapp()-gtuser-gtid

else

$this-gtupdate time=time()

return true

else

return false

33 Displaying Posts 25

When we save a post we want to update the tbl tag table to reflect the change of tagfrequencies We can do this work in the afterSave() method which is automaticallyinvoked by Yii after a post is successfully saved into the database

protected function afterSave()

parentafterSave()

Tagmodel()-gtupdateFrequency($this-gt oldTags $this-gttags)

private $ oldTags

protected function afterFind()

parentafterFind()

$this-gt oldTags=$this-gttags

In the implementation because we want to detect if the user changes the tags in case he isupdating an existing post we need to know what the old tags are For this reason we alsowrite the afterFind() method to keep the old tags in the variable oldTags The methodafterFind() is invoked automatically by Yii when an AR record is populated with thedata from database

We are not going to give details of the TagupdateFrequency() method here Interestedreaders may refer to the file wwwrootyiidemosblogprotectedmodelsTagphp

33 Displaying Posts

In our blog application a post may be displayed among a list of posts or by itself Theformer is implemented as the index operation while the latter the view operation In thissection we customize both operations to fulfill our initial requirements

331 Customizing view Operation

The view operation is implemented by the actionView() method in PostController Itsdisplay is generated by the view view with the view file wwwrootblogprotectedviews

postviewphp

Below is the relevant code implementing the view operation in PostController

public function actionView()

26 3 Post Management

$this-gtrender(rsquoviewrsquoarray(

$post=$this-gtloadModel()

$this-gtrender(rsquoviewrsquoarray(

rsquomodelrsquo=gt$post

))

))

private $ model

public function loadModel()

if($this-gt model===null)

if(isset($ GET[rsquoidrsquo]))

if(Yiiapp()-gtuser-gtisGuest)

$condition=rsquostatus=rsquoPostSTATUS PUBLISHED

rsquo OR status=rsquoPOSTSTATUS ARCHIVED

else

$condition=rsquorsquo

$this-gt model=Postmodel()-gtfindbyPk($ GET[rsquoidrsquo] $condition)

if($this-gt model===null)

throw new CHttpException(404rsquoThe requested page does not existrsquo)

return $this-gt model

Our change mainly lies in the loadModel() method In this method we query the Post

table according to the id GET parameter If the post is not found or if it is not publishedor archived (when the user is a guest) we will throw a 404 HTTP error Otherwise thepost object is returned to actionView() which in turn passes the post object to the viewscript for further display

Tip Yii captures HTTP exceptions (instances of CHttpException) and displaysthem in either predefined templates or customized error views The skeleton appli-cation generated by yiic already contains a customized error view in wwwroot

blogprotectedviewssiteerrorphp We can modify this file if we want tofurther customize the error display

The change in the view script is mainly about ajdusting the formatting and styles of thepost display We will not go into details here Interested readers may refer to wwwroot

blogprotectedviewspostviewphp

33 Displaying Posts 27

332 Customizing index Operation

Like the view operation we customize the index operation in two places the actionIndex()

method in PostController and the view file wwwrootblogprotectedviewspostindex

php We mainly need to add the support for displaying a list of posts that are associatedwith a specified tag

Below is the modified actionIndex() method in PostController

public function actionIndex()

$criteria=new CDbCriteria(array(

rsquoconditionrsquo=gtrsquostatus=rsquoPostSTATUS PUBLISHED

rsquoorderrsquo=gtrsquoupdate time DESCrsquo

rsquowithrsquo=gtrsquocommentCountrsquo

))

if(isset($ GET[rsquotagrsquo]))

$criteria-gtaddSearchCondition(rsquotagsrsquo$ GET[rsquotagrsquo])

$dataProvider=new CActiveDataProvider(rsquoPostrsquo array(

rsquopaginationrsquo=gtarray(

rsquopageSizersquo=gtYiiapp()-gtparams[rsquopostsPerPagersquo]

)

rsquocriteriarsquo=gt$criteria

))

$this-gtrender(rsquoindexrsquoarray(

rsquodataProviderrsquo=gt$dataProvider

))

In the above we first create a query criteria for retrieving post list The criteria statesthat only published posts should be returned and they should be sorted according to theirupdate time in descending order Because when displaying a post in the list we want toshow how many comments the post has received in the criteria we also specify to bringback commentCount which if you remember is a relation declared in Postrelations()

In case when a user wants to see posts with a specific tag we would add a search conditionto the criteria to look for the specified tag

Using the query criteria we create a data provider which mainly serves for three purposesFirst it does pagination of the data when too many results may be returned Herewe customize the pagination by setting the page size to be an application parameterpostsPerPage Second it does sorting according to the user request And finally it feedsthe paginated and sorted data to widgets or view code for presentation

28 3 Post Management

After we finish with actionIndex() we modify the index view as follows Our change ismainly about adding the h1 header when the user specifies to display posts with a tag

ltphp if(empty($ GET[rsquotagrsquo])) gt

lth1gtPosts Tagged with ltigtltphp echo CHtmlencode($ GET[rsquotagrsquo]) gtltigtlth1gt

ltphp endif gt

ltphp $this-gtwidget(rsquoziiwidgetsCListViewrsquo array(

rsquodataProviderrsquo=gt$dataProvider

rsquoitemViewrsquo=gtrsquo viewrsquo

rsquotemplatersquo=gtitemsnpager)) gt

Note that in the above we use [CListView] to display the post list This widget requires apartial view to display the detail of each individual post Here we specify the partial viewto be view which means the file wwwrootblogprotectedviewspost viewphp In thisview script we can acccess the post instance being displayed via a local variable named$data

34 Managing Posts

Managing posts mainly refers to listing posts in an administrative view that allows us tosee posts with all statuses updating them and deleting them They are accomplished bythe admin operation and the delete operation respectively The code generated by yiic

does not need much modification Below we mainly explain how these two operations areimplemented

341 Listing Posts in Tabular View

The admin operation shows posts with all statuses in a tabular view The view supportssorting and pagination The following is the actionAdmin() method in PostController

public function actionAdmin()

$dataProvider=new CActiveDataProvider(rsquoPostrsquo array(

rsquosortrsquo=gtarray(

rsquodefaultOrderrsquo=gtrsquostatus update time DESCrsquo

)

))

$this-gtrender(rsquoadminrsquoarray(

rsquodataProviderrsquo=gt$dataProvider

))

34 Managing Posts 29

The above code is very similar to that in actionIndex() The main difference is in thequery criteria Here we do not specify any particular query criteria since we want to seeall posts We do customize the sort property of the data provider so that by default theposts are shown in the order of their statuses and update time

Below is the code for the admin view

lth1gtManage Postslth1gt

ltphp $this-gtwidget(rsquoziiwidgetsgridCGridViewrsquo array(

rsquodataProviderrsquo=gt$dataProvider

rsquocolumnsrsquo=gtarray(

array(

rsquonamersquo=gtrsquotitlersquo

rsquotypersquo=gtrsquorawrsquo

rsquovaluersquo=gtrsquoCHtmllink(CHtmlencode($data-gttitle) $data-gturl)rsquo

)

array(

rsquonamersquo=gtrsquostatusrsquo

rsquovaluersquo=gtrsquoLookupitem(PostStatus$data-gtstatus)rsquo

)

rsquocreate timedatetimersquo

rsquoupdate timedatetimersquo

array(

rsquoclassrsquo=gtrsquoCButtonColumnrsquo

)

)

)) gt

We use [CGridView] to display the posts It allows us to sort by a column and paginatethrough the posts if there are too many to be displayed in a single page Our change ismainly about how to display each column For example for the title column we specifythat it should be displayed as a hyperlink that points to the detailed view of the post

Tip When displaying text we call CHtmlencode() to encode HTML entities init This prevents from cross-site scripting attack

342 Deleting Posts

In the admin data grid there is a delete button in each row Clicking on the button shoulddelete the corresponding post Internally this triggers the delete action implemented asfollows

30 3 Post Management

public function actionDelete()

if(Yiiapp()-gtrequest-gtisPostRequest)

we only allow deletion via POST request

$this-gtloadModel()-gtdelete()

if(isset($ POST[rsquoajaxrsquo]))

$this-gtredirect(array(rsquoindexrsquo))

else

throw new CHttpException(400rsquoInvalid request Please do not repeat this request againrsquo)

The above code is the one generated by the yiic tool without any change We wouldlike to explain a little bit more about the checking on $ POST[rsquoajaxrsquo] The [CGridView]widget has a very nice feature that its sorting pagination and deletion operations are alldone in AJAX mode by default That means the whole page does not get reloaded if anyof the above operations is performed However it is also possible that the widget runs innon-AJAX mode (by setting its ajaxUpdate property to be false or disabling JavaScript onthe client side) It is necessary for the delete action to differentiate these two scenarios ifthe delete request is made via AJAX we should not redirect the user browser otherwisewe should

Deleting a post should also cause the deletion of all comments for that post In additionwe should also update the tbl tag table regarding the tags for the deleted post Both ofthese tasks can be achieved by writing an afterDelete method in the Post model class asfollows

protected function afterDelete()

parentafterDelete()

Commentmodel()-gtdeleteAll(rsquopost id=rsquo$this-gtid)

Tagmodel()-gtupdateFrequency($this-gttags rsquorsquo)

The above code is very straightforward it first deletes all those comments whose post id

is the same as the ID of the deleted post it then updates the tbl tag table for the tags

of the deleted post

34 Managing Posts 31

Tip We have to explicitly delete all comments for the deleted post here becauseSQLite does not really support foreign key constraints In a DBMS that supportsthis constraint (such as MySQL PostgreSQL) the foreign key constraint can be setup such that the DBMS automatically deletes the related comments if the post isdeleted In that case we no longer this explicit deletion call in our code

32 3 Post Management

Chapter 4

Comment Management

41 Customizing Comment Model

For the Comment model we mainly need to customize the rules() and attributeLabels()

methods The attributeLabels() method returns a mapping between attribute namesand attribute labels We do not need to touch relations() since the code generated bythe yiic tool is good enough

411 Customizing rules() Method

We first customize the validation rules generated by the yiic tool The following rules areused for comments

public function rules()

return array(

array(rsquocontent author emailrsquo rsquorequiredrsquo)

array(rsquoauthor email urlrsquo rsquolengthrsquo rsquomaxrsquo=gt128)

array(rsquoemailrsquorsquoemailrsquo)

array(rsquourlrsquorsquourlrsquo)

)

In the above we specify that the author email and content attributes are required thelength of author email and url cannot exceed 128 the email attribute must be a validemail address and the url attribute must be a valid URL

412 Customizing attributeLabels() Method

We then customize the attributeLabels() method to declare the label display for eachmodel attribute This method returns an array consisting of name-label pairs When wecall CHtmlactiveLabel() to display an attribute label

34 4 Comment Management

public function attributeLabels()

return array(

rsquoidrsquo =gt rsquoIdrsquo

rsquocontentrsquo =gt rsquoCommentrsquo

rsquostatusrsquo =gt rsquoStatusrsquo

rsquocreate timersquo =gt rsquoCreate Timersquo

rsquoauthorrsquo =gt rsquoNamersquo

rsquoemailrsquo =gt rsquoEmailrsquo

rsquourlrsquo =gt rsquoWebsitersquo

rsquopost idrsquo =gt rsquoPostrsquo

)

Tip If the label for an attribute is not declared in attributeLabels() an algo-rithm will be used to generate an appropriate label For example a label VerifyCode will be generated for attributes verify code or verifyCode

413 Customizing Saving Process

Because we want to record the creation time of a comment we override the beforeSave()

method of Comment like we do for the Post model

protected function beforeSave()

if(parentbeforeSave())

if($this-gtisNewRecord)

$this-gtcreate time=time()

return true

else

return false

42 Creating and Displaying Comments

In this section we implement the comment display and creation features

421 Displaying Comments

Instead of displaying and creating comments on individual pages we use the post detailpage (generated by the view action of PostController) Below the post content display

42 Creating and Displaying Comments 35

we display first a list of comments belonging to that post and then a comment creationform

In order to display comments on the post detail page we modify the view script wwwrootblogprotectedviewspostviewphp as follows

ltdiv id=commentsgt

ltphp if($model-gtcommentCountgt=1) gt

lth3gt

ltphp echo $model-gtcommentCount rsquocomment(s)rsquo gt

lth3gt

ltphp $this-gtrenderPartial(rsquo commentsrsquoarray(

rsquopostrsquo=gt$model

rsquocommentsrsquo=gt$model-gtcomments

)) gt

ltphp endif gt

ltdivgt

In the above we call renderPartial() to render a partial view named comments to displaythe list of comments belonging to the current post Note that in the view we use theexpression $model-gtcomments to retrieve the comments for the post This is valid becausewe have declared a comments relation in the Post class Evaluating this expression wouldtrigger an implicit JOIN database query to bring back the proper comments This featureis known as lazy relational query

The partial view comments is not very interesting It mainly goes through every commentand displays the detail of it Interested readers may refer to wwwrootyiidemosblog

protectedpost commentsphp

422 Creating Comments

To handle comment creation we first modify the actionView() method of PostController

as follows

public function actionView()

$post=$this-gtloadModel()

$comment=$this-gtnewComment($post)

$this-gtrender(rsquoviewrsquoarray(

rsquomodelrsquo=gt$post

rsquocommentrsquo=gt$comment

))

36 4 Comment Management

protected function newComment($post)

$comment=new Comment

if(isset($ POST[rsquoCommentrsquo]))

$comment-gtattributes=$ POST[rsquoCommentrsquo]

if($post-gtaddComment($comment))

if($comment-gtstatus==CommentSTATUS PENDING)

Yiiapp()-gtuser-gtsetFlash(rsquocommentSubmittedrsquorsquoThank yoursquo)

$this-gtrefresh()

return $comment

In the above we call the newComment() method before we render view In the newComment()

method we generate a Comment instance and check if the comment form is submitted If sowe try to add the comment for the post by calling $post-gtaddComment($comment) If it goesthrough we refresh the post detail page In case the comment needs to be approved we willshow a flash message to indicate this decision A flash message is usually a confirmationmessage displayed to end users If the user clicks on the refresh button of his browser themessage will disappear

We also need to modify wwwrootblogprotectedviewspostviewphp furthermore

ltdiv id=commentsgt

lth3gtLeave a Commentlth3gt

ltphp if(Yiiapp()-gtuser-gthasFlash(rsquocommentSubmittedrsquo)) gt

ltdiv class=successgt

ltphp echo Yiiapp()-gtuser-gtgetFlash(rsquocommentSubmittedrsquo) gt

ltdivgt

ltphp else gt

ltphp $this-gtrenderPartial(rsquocomment formrsquoarray(

rsquomodelrsquo=gt$comment

)) gt

ltphp endif gt

ltdivgtlt-- comments --gt

43 Managing Comments 37

In the above code we display the flash message if it is available If not we displaythe comment input form by rendering the partial view wwwrootblogprotectedviews

comment formphp

43 Managing Comments

Comment management includes updating deleting and approving comments These op-erations are implemented as actions in the CommentController class

431 Updating and Deleting Comments

The code generated by yiic for updating and deleting comments remains largely un-changed

432 Approving Comments

When comments are newly created they are in pending approval status and need to beapproved in order to be visible to guest users Approving a comment is mainly aboutchanging the status column of the comment

We create an actionApprove() method in CommentController as follows

public function actionApprove()

if(Yiiapp()-gtrequest-gtisPostRequest)

$comment=$this-gtloadModel()

$comment-gtapprove()

$this-gtredirect(array(rsquoindexrsquo))

else

throw new CHttpException(400rsquoInvalid requestrsquo)

In the above when the approve action is invoked via a POST request we call the approve()

method defined in the Comment model to change the status We then redirect the userbrowser to the page displaying the post that this comment belongs to

We also modify the actionIndex() method of Comment to show all comments We wouldlike to see comments pending approved to show up first

public function actionIndex()

38 4 Comment Management

$dataProvider=new CActiveDataProvider(rsquoCommentrsquo array(

rsquocriteriarsquo=gtarray(

rsquowithrsquo=gtrsquopostrsquo

rsquoorderrsquo=gtrsquotstatus tcreate time DESCrsquo

)

))

$this-gtrender(rsquoindexrsquoarray(

rsquodataProviderrsquo=gt$dataProvider

))

Notice that in the above code because both tbl post and tbl comment have columns statusand create time we need to disambiguate the corresponding column reference by prefixingthem with table alias names As described in the guide the alias for the primary table ina relational query is always t Therefore we are prefixing t to the status and create time

columns in the above code

Like the post index view the index view for CommentController uses [CListView] to displaythe comment list which in turn uses the partial view wwwrootblogprotectedviews

comment viewphp to display the detail of each individual comment We will not go intodetails here Interested readers may refer to the corresponding file in the blog demowwwrootyiidemosblogprotectedviewscomment viewphp

Chapter 5

Portlets

51 Customizing Page Layout

Features like rdquothe most recent commentsrdquo rdquotag cloudrdquo are better to be implemented inportlets A portlet is a pluggable user interface component that renders a fragment ofHTML code In this section we describe how to set up the portlet architecture for ourblog application

Before we go into details about developing portlets we first need to adjust our page layoutso that the portlets can be placed in a side bar

A page layout determines how to share common sections (eg header footer) amongdifferent pages and how to embed contents that are specific for different pages In Yiia page layout is represented as a view under the protectedviewslayouts folder Whenwe call the render() method in a controller the rendering result will be automaticallyembedded into this layout view before it is displayed to end users The default layoutview is named as main which means the wwwrootblogprotectedviewslayoutsmain

php file in our blog application

We could directly modify the main layout to add a side bar However doing so wouldmake some irrelevant pages such as about contact pages to have a side bar which is notalways desirable For this reason we need different layouts for different pages

We thus create two different layouts column1 and column2 The former displays the pagein a single column while the latter displays two columns in the page one of which is theside bar We will use column2 layout for the post and comment pages and column1 forthe rest of the pages Because both column1 and column2 still share the same header andfooter sections we let them to share the main layout

Based on the above description we create the column1 layout as follows

ltphp $this-gtbeginContent(rsquolayoutsmainrsquo) gt

ltdiv class=containergt

40 5 Portlets

ltdiv id=contentgt

ltphp echo $content gt

ltdivgtlt-- content --gt

ltdivgt

ltphp $this-gtendContent() gt

and the column2 layout as follows

ltphp $this-gtbeginContent(rsquolayoutsmainrsquo) gt

ltdiv class=containergt

ltdiv class=span-18gt

ltdiv id=contentgt

ltphp echo $content gt

ltdivgtlt-- content --gt

ltdivgt

ltdiv class=span-6 lastgt

ltdiv id=sidebargt

portlets to be placed here

ltdivgtlt-- sidebar --gt

ltdivgt

ltdivgt

ltphp $this-gtendContent() gt

As we can see both column1 and column2 are enclosed in a pair of beginContent andendContent calls They essentially use the specified view layoutsmain to decorate theenclosed content In all these layout views the variable $content refers to the dynamiccontent to be embedded into the layout

Besides customizing the layout view file we also need to adjust the CSS file wwwroot

blogcssmaincss so that the overall appearance would look like what we see in the blogdemo We will not go into details here

Info The skeleton application generated by the yiic tool uses the BluePrint CSSframework It provides a nice font reset and a robust grid system For more detailson how to use this framework please refer to its documentation

After creating the new layouts we need to set the layout property of controllers to makeuse of them So far we have three controllers SiteController PostController andCommentController We set SiteController to use column1 as the default layout whilethe rest two of the controllers use column2 for example

class PostController extends Controller

52 Creating User Menu Portlet 41

public $layout=rsquocolumn2rsquo

Tip If some view of the controller does not use the same layout as declaredby the layout property it may customize it in the view by a simple assignment$this-gtlayout=rsquonewlayoutrsquo

52 Creating User Menu Portlet

Based on the requirements analysis we need three different portlets the rdquouser menurdquoportlet the rdquotag cloudrdquo portlet and the rdquorecent commentsrdquo portlet We will develop theseportlets by extending the [CPortlet] widget provided by Yii

In this section we will develop our first concrete portlet - the user menu portlet whichdisplays a list of menu items that are only available to authenticated users The menucontains four items

bull Approve Comments a hyperlink that leads to a list of comments pending approval

bull Create New Post a hyperlink that leads to the post creation page

bull Manage Posts a hyperlink that leads to the post management page

bull Logout a link button that would log out the current user

521 Creating UserMenu Class

We create the UserMenu class to represent the logic part of the user menu portlet Theclass is saved in the file wwwrootblogprotectedcomponentsUserMenuphp which has thefollowing content

Yiiimport(rsquoziiwidgetsCPortletrsquo)

class UserMenu extends CPortlet

public function init()

$this-gttitle=CHtmlencode(Yiiapp()-gtuser-gtname)

parentinit()

42 5 Portlets

protected function renderContent()

$this-gtrender(rsquouserMenursquo)

The UserMenu class extends from the CPortlet class that we created previously It overridesboth the init() method and the renderContent() method of CPortlet The former setsthe portlet title to be the name of the current user the latter generates the portlet bodycontent by rendering a view named userMenu

Tip Notice that we have to explicitly include the CPortlet class by calling Yii

import() before we refer to it the first time This is because CPortlet is part of thezii project ndash the official extension library for Yii For performance considerationclasses in this project are not listed as core classes Therefore we have to import itbefore we use it the first time

522 Creating userMenu View

Next we create the userMenu view which is saved in the file wwwrootblogprotected

componentsviewsuserMenuphp

ltulgt

ltligtltphp echo CHtmllink(rsquoCreate New Postrsquoarray(rsquopostcreatersquo)) gtltligt

ltligtltphp echo CHtmllink(rsquoManage Postsrsquoarray(rsquopostadminrsquo)) gtltligt

ltligtltphp echo CHtmllink(rsquoApprove Commentsrsquoarray(rsquocommentindexrsquo))

rsquo (rsquo Commentmodel()-gtpendingCommentCount rsquo)rsquo gtltligt

ltligtltphp echo CHtmllink(rsquoLogoutrsquoarray(rsquositelogoutrsquo)) gtltligt

ltulgt

Info By default view files for a widget should be placed under the views sub-directory of the directory containing the widget class file The file name must bethe same as the view name

523 Using UserMenu Portlet

It is time for us to make use of our newly completed UserMenu portlet We modify thelayout view file wwwrootblogprotectedviewslayoutscolumn2php as follows

53 Creating Tag Cloud Portlet 43

ltdiv id=sidebargt

ltphp if(Yiiapp()-gtuser-gtisGuest) $this-gtwidget(rsquoUserMenursquo) gt

ltdivgt

In the above we call the widget() method to generate and execute an instance of theUserMenu class Because the portlet should only be displayed to authenticated users weonly call widget() when the isGuest property of the current user is false (meaning theuser is authenticated)

524 Testing UserMenu Portlet

Letrsquos test what we have so far

1 Open a browser window and enter the URL httpwwwexamplecomblogindex

php Verify that there is nothing displayed in the side bar section of the page

2 Click on the Login hyperlink and fill out the login form to login If successful verifythat the UserMenu portlet appears in the side bar and the portlet has the usernameas its title

3 Click on the rsquoLogoutrsquo hyperlink in the UserMenu portlet Verify that the logout actionis successful and the UserMenu portlet disappears

525 Summary

What we have created is a portlet that is highly reusable We can easily reuse it in adifferent project with little or no modification Moreover the design of this portlet followsclosely the philosophy that logic and presentation should be separated While we did notpoint this out in the previous sections such practice is used nearly everywhere in a typicalYii application

53 Creating Tag Cloud Portlet

Tag cloud displays a list of post tags with visual decorations hinting the popularity of eachindividual tag

531 Creating TagCloud Class

We create the TagCloud class in the file wwwrootblogprotectedcomponentsTagCloud

php The file has the following content

44 5 Portlets

Yiiimport(rsquoziiwidgetsCPortletrsquo)

class TagCloud extends CPortlet

public $title=rsquoTagsrsquo

public $maxTags=20

protected function renderContent()

$tags=Tagmodel()-gtfindTagWeights($this-gtmaxTags)

foreach($tags as $tag=gt$weight)

$link=CHtmllink(CHtmlencode($tag) array(rsquopostindexrsquorsquotagrsquo=gt$tag))

echo CHtmltag(rsquospanrsquo array(

rsquoclassrsquo=gtrsquotagrsquo

rsquostylersquo=gtfont-size$weightpt) $link)n

Unlike the UserMenu portlet the TagCloud portlet does not use a view Instead its presen-tation is done in the renderContent() method This is because the presentation does notcontain much HTML tags

We display each tag as a hyperlink to the post index page with the corresponding tagparameter The font size of each tag link is adjusted according to their relative weightamong other tags If a tag has higher frequency value than the other it will have a biggerfont size

532 Using TagCloud Portlet

Usage of the TagCloud portlet is very simple We modify the layout file wwwrootblog

protectedviewslayoutsmainphp as follows

ltdiv id=sidebargt

ltphp if(Yiiapp()-gtuser-gtisGuest) $this-gtwidget(rsquoUserMenursquo) gt

ltphp $this-gtwidget(rsquoTagCloudrsquo array(

rsquomaxTagsrsquo=gtYiiapp()-gtparams[rsquotagCloudCountrsquo]

)) gt

ltdivgt

54 Creating Recent Comments Portlet 45

54 Creating Recent Comments Portlet

In this section we create the last portlet that displays a list of comments recently pub-lished

541 Creating RecentComments Class

We create the RecentComments class in the file wwwrootblogprotectedcomponentsRecentCommentsphp The file has the following content

Yiiimport(rsquoziiwidgetsCPortletrsquo)

class RecentComments extends CPortlet

public $title=rsquoRecent Commentsrsquo

public $maxComments=10

public function getRecentComments()

return Commentmodel()-gtfindRecentComments($this-gtmaxComments)

protected function renderContent()

$this-gtrender(rsquorecentCommentsrsquo)

In the above we invoke the findRecentComments method which is defined in the Comment

class as follows

class Comment extends CActiveRecord

public function findRecentComments($limit=10)

return $this-gtwith(rsquopostrsquo)-gtfindAll(array(

rsquoconditionrsquo=gtrsquotstatus=rsquoselfSTATUS APPROVED

rsquoorderrsquo=gtrsquotcreate time DESCrsquo

rsquolimitrsquo=gt$limit

))

46 5 Portlets

542 Creating recentComments View

The recentComments view is saved in the file wwwrootblogprotectedcomponentsviews

recentCommentsphp It simply displays every comment returned by the RecentComments

getRecentComments() method

543 Using RecentComments Portlet

We modify the layout file wwwrootblogprotectedviewslayoutsmainphp to embedthis last portlet

ltdiv id=sidebargt

ltphp if(Yiiapp()-gtuser-gtisGuest) $this-gtwidget(rsquoUserMenursquo) gt

ltphp $this-gtwidget(rsquoTagCloudrsquo array(

rsquomaxTagsrsquo=gtYiiapp()-gtparams[rsquotagCloudCountrsquo]

)) gt

ltphp $this-gtwidget(rsquoRecentCommentsrsquo array(

rsquomaxCommentsrsquo=gtYiiapp()-gtparams[rsquorecentCommentCountrsquo]

)) gt

ltdivgt

Chapter 6

Final Work

61 Beautifying URLs

The URLs linking various pages of our blog application currently look ugly For examplethe URL for the page showing a post looks like the following

indexphpr=postshowampid=1

In this section we describe how to beautify these URLs and make them SEO-friendlyOur goal is to be able to use the following URLs in the application

1 indexphppostsyii leads to the page showing a list of posts with tag yii

2 indexphppost2A+Test+Post leads to the page showing the detail of the postwith ID 2 whose title is A Test Post

3 indexphppostupdate1 leads to the page that allows updating the post with ID1

Note that in the second URL format we include the post title in the URL This is mainlyto make the URL SEO friendly It is said that search engines may also respect the wordsfound in a URL when it is being indexed

To achieve our goal we modify the application configuration as follows

return array(

rsquocomponentsrsquo=gtarray(

rsquourlManagerrsquo=gtarray(

rsquourlFormatrsquo=gtrsquopathrsquo

rsquorulesrsquo=gtarray(

48 6 Final Work

rsquopostltidd+gtlttitlegtrsquo=gtrsquopostviewrsquorsquopostslttaggtrsquo=gtrsquopostindexrsquo

)

)

)

)

In the above we configure the urlManager component by setting its urlFormat propertyto be path and adding a set of rules

The rules are used by urlManager to parse and create the URLs in the desired formatFor example the first rule says that if a URL indexphppostsyii is requested theurlManager component should be responsible to dispatch the request to the route post

index and generate a tag GET parameter with the value yii On the other hand whencreating a URL with the route postindex and parameter tag the urlManager componentwill also use this rule to generate the desired URL indexphppostsyii For this reasonwe say that urlManager is a two-way URL manager

The urlManager component can further beautify our URLs such as hiding indexphp inthe URLs appending suffix like html to the URLs We can obtain these features easilyby configuring various properties of urlManager in the application configuration For moredetails please refer to the Guide

62 Logging Errors

A production Web application often needs sophisticated logging for various events In ourblog application we would like to log the errors occurring when it is being used Sucherrors could be programming mistakes or usersrsquo misuse of the system Logging these errorswill help us to improve the blog application

We enable the error logging by modifying the application configuration as follows

return array(

rsquopreloadrsquo=gtarray(rsquologrsquo)

rsquocomponentsrsquo=gtarray(

rsquologrsquo=gtarray(

rsquoclassrsquo=gtrsquoCLogRouterrsquo

rsquoroutesrsquo=gtarray(

array(

rsquoclassrsquo=gtrsquoCFileLogRoutersquo

rsquolevelsrsquo=gtrsquoerror warningrsquo

63 Final Tune-up and Deployment 49

)

)

)

)

)

With the above configuration if an error or warning occurs detailed information will belogged and saved in a file located under the directory wwwrootblogprotectedruntime

The log component offers more advanced features such as sending log messages to a listof email addresses displaying log messages in JavaScript console window etc For moredetails please refer to the Guide

63 Final Tune-up and Deployment

We are close to finish our blog application Before deployment we would like to do sometune-ups

631 Changing Home Page

We change to use the post list page as the home page We modify the application config-uration as follows

return array(

rsquodefaultControllerrsquo=gtrsquopostrsquo

)

Tip Because PostController already declares list to be its default action whenwe access the home page of the application we will see the result generated by thelist action of the post controller

632 Enabling Schema Caching

Because ActiveRecord relies on the metadata about tables to determine the column infor-mation it takes time to read the metadata and analyze it This may not be a problemduring development stage but for an application running in production mode it is a totalwaste of time if the database schema does not change Therefore we should enable theschema caching by modifying the application configuration as follows

50 6 Final Work

return array(

rsquocomponentsrsquo=gtarray(

rsquocachersquo=gtarray(

rsquoclassrsquo=gtrsquoCDbCachersquo

)

rsquodbrsquo=gtarray(

rsquoclassrsquo=gtrsquosystemdbCDbConnectionrsquo

rsquoconnectionStringrsquo=gtrsquosqlitewwwrootblogprotecteddatablogdbrsquo

rsquoschemaCachingDurationrsquo=gt3600

)

)

)

In the above we first add a cache component which uses a default SQLite database as thecaching storage If our server is equipped with other caching extensions such as APCwe could change to use them as well We also modify the db component by setting itsschemaCachingDuration property to be 3600 which means the parsed database schemadata can remain valid in cache for 3600 seconds

633 Disabling Debugging Mode

We modify the entry script file wwwrootblogindexphp by removing the line definingthe constant YII DEBUG This constant is useful during development stage because it allowsYii to display more debugging information when an error occurs However when theapplication is running in production mode displaying debugging information is not agood idea because it may contain sensitive information such as where the script file islocated and the content in the file etc

634 Deploying the Application

The final deployment process manly involves copying the directory wwwrootblog to thetarget directory The following checklist shows every needed step

1 Install Yii in the target place if it is not available

2 Copy the entire directory wwwrootblog to the target place

3 Edit the entry script file indexphp by pointing the $yii variable to the new Yiibootstrap file

4 Edit the file protectedyiicphp by setting the $yiic variable to be the new Yiiyiicphp file

64 Future Enhancements 51

5 Change the permission of the directories assets and protectedruntime so that theyare writable by the Web server process

64 Future Enhancements

641 Using a Theme

Without writing any code our blog application is already themeable To use a themewe mainly need to develop the theme by writing customized view files in the theme Forexample to use a theme named classic that uses a different page layout we would createa layout view file wwwrootblogthemesclassicviewslayoutsmainphp We also needto change the application configuration to indicate our choice of the classic theme

return array(

rsquothemersquo=gtrsquoclassicrsquo

)

642 Internationalization

We may also internationalize our blog application so that its pages can be displayed indifferent languages This mainly involves efforts in two aspects

First we may create view files in different languages For example for the list pageof PostController we can create a view file wwwrootblogprotectedviewspostzh cn

listphp When the application is configured to use simplified Chinese (the language codeis zh cn) Yii will automatically use this new view file instead of the original one

Second we may create message translations for those messages generated by code Themessage translations should be saved as files under the directory wwwrootblogprotected

messages We also need to modify the code where we use text strings by enclosing themin the method call Yiit()

For more details about internationalization please refer to the Guide

643 Improving Performance with Cache

While the Yii framework itself is very efficient it is not necessarily true that an applicationwritten in Yii efficient There are several places in our blog application that we can improvethe performance For example the tag clould portlet could be one of the performancebottlenecks because it involves complex database query and PHP logic

52 6 Final Work

We can make use of the sophisticated caching feature provided by Yii to improve theperformance One of the most useful components in Yii is COutputCache which caches afragment of page display so that the underlying code generating the fragment does not needto be executed for every request For example in the layout file wwwrootblogprotected

viewslayoutsmainphp we can enclose the tag cloud portlet with COutputCache

ltphp if($this-gtbeginCache(rsquotagCloudrsquo array(rsquodurationrsquo=gt3600))) gt

ltphp $this-gtwidget(rsquoTagCloudrsquo array(

rsquomaxTagsrsquo=gtYiiapp()-gtparams[rsquotagCloudCountrsquo]

)) gt

ltphp $this-gtendCache() gt

With the above code the tag cloud display will be served from cache instead of beinggenerated on-the-fly for every request The cached content will remain valid in cache for3600 seconds

644 Adding New Features

Our blog application only has very basic functionalities To become a complete blogsystem more features are needed for example calendar portlet email notifications postcategorization archived post portlet and so on We will leave the implementation of thesefeatures to interested readers

  • Contents
  • License
  • Getting Started
    • Building a Blog System using Yii
    • Testdriving with Yii
      • Installing Yii
      • Creating Skeleton Application
      • Application Workflow
        • Requirements Analysis
        • Overall Design
          • Initial Prototyping
            • Setting Up Database
              • Creating Database
              • Establishing Database Connection
                • Scaffolding
                • Authenticating User
                • Summary
                  • Post Management
                    • Customizing Post Model
                      • Customizing rules() Method
                      • Customizing relations() Method
                      • Representing Status in Text
                        • Creating and Updating Posts
                          • Customizing Access Control
                          • Customizing create and update Operations
                            • Displaying Posts
                              • Customizing view Operation
                              • Customizing index Operation
                                • Managing Posts
                                  • Listing Posts in Tabular View
                                  • Deleting Posts
                                      • Comment Management
                                        • Customizing Comment Model
                                          • Customizing rules() Method
                                          • Customizing attributeLabels() Method
                                          • Customizing Saving Process
                                            • Creating and Displaying Comments
                                              • Displaying Comments
                                              • Creating Comments
                                                • Managing Comments
                                                  • Updating and Deleting Comments
                                                  • Approving Comments
                                                      • Portlets
                                                        • Customizing Page Layout
                                                        • Creating User Menu Portlet
                                                          • Creating UserMenu Class
                                                          • Creating userMenu View
                                                          • Using UserMenu Portlet
                                                          • Testing UserMenu Portlet
                                                          • Summary
                                                            • Creating Tag Cloud Portlet
                                                              • Creating TagCloud Class
                                                              • Using TagCloud Portlet
                                                                • Creating Recent Comments Portlet
                                                                  • Creating RecentComments Class
                                                                  • Creating recentComments View
                                                                  • Using RecentComments Portlet
                                                                      • Final Work
                                                                        • Beautifying URLs
                                                                        • Logging Errors
                                                                        • Final Tune-up and Deployment
                                                                          • Changing Home Page
                                                                          • Enabling Schema Caching
                                                                          • Disabling Debugging Mode
                                                                          • Deploying the Application
                                                                            • Future Enhancements
                                                                              • Using a Theme
                                                                              • Internationalization
                                                                              • Improving Performance with Cache
                                                                              • Adding New Features
Page 15: Yii Blog 1.1.0 Demo Tutor

8 1 Getting Started

Chapter 2

Initial Prototyping

21 Setting Up Database

Having created a skeleton application and finished the database design in this section wewill create the blog database and establish the connection to it in the skeleton application

211 Creating Database

We choose to create a SQLite database Because the database support in Yii is built on topof PDO we can easily switch to use a different type of DBMS (eg MySQL PostgreSQL)without the need to change our application code

We create the database file blogdb under the directory wwwrootblogprotecteddataNote that both the directory and the database file have to be writable by the Web serverprocess as required by SQLite We may simply copy the database file from the blogdemo in our Yii installation which is located at wwwrootyiidemosblogprotecteddatablogdb We may also generate the database by executing the SQL statements in the filewwwrootyiidemosblogprotecteddataschemasqlitesql

Tip To execute SQL statements we may use the sqlite3 command line tool thatcan be found in the SQLite official website

212 Establishing Database Connection

To use the blog database in the skeleton application we created we need to modify itsapplication configuration which is stored in the PHP script wwwrootblogprotected

configmainphp The script returns an associative array consisting of name-value pairseach of which is used to initialize a writable property of the application instance

We configure the db component as follows

10 2 Initial Prototyping

return array(

rsquocomponentsrsquo=gtarray(

rsquodbrsquo=gtarray(

rsquoconnectionStringrsquo=gtrsquosqlitewwwrootblogprotecteddatablogdbrsquo

rsquotablePrefixrsquo=gtrsquotbl rsquo

)

)

)

The above configuration says that we have a db application component whose connectionStringproperty should be initialized as sqlitewwwrootblogprotecteddatablogdb and whosetablePrefix property should be tbl

With this configuration we can access the DB connection object using Yiiapp()-gtdb

at any place in our code Note that Yiiapp() returns the application instance that wecreate in the entry script If you are interested in possible methods and properties that theDB connection has you may refer to its class reference However in most cases we are notgoing to use this DB connection directly Instead we will use the so-called ActiveRecordto access the database

We would like to explain a bit more about the tablePrefix property that we set in theconfiguration This tells the db connection that it should respect the fact we are usingtbl as the prefix to our database table names In particular if in a SQL statement thereis a token enclosed within double curly brackets (eg post then the db connectionshould translate it into a name with the table prefix (eg tbl post) before sending it toDBMS for execution This feature is especially useful if in future we need to modify thetable name prefix without touching our source code For example if we are developing ageneric content management system (CMS) we may exploit this feature so that when itis being installed in a new environment we can allow users to choose a table prefix theylike

22 Scaffolding 11

Tip If you want to use MySQL instead of SQLite to store data you may cre-ate a MySQL database named blog using the SQL statements in wwwrootyii

demosblogprotecteddataschemamysqlsql Then modify the applicationconfiguration as follows

return array(

rsquocomponentsrsquo=gtarray(

rsquodbrsquo=gtarray(

rsquoconnectionStringrsquo =gt rsquomysqlhost=localhostdbname=blogrsquo

rsquoemulatePreparersquo =gt true

rsquousernamersquo =gt rsquorootrsquo

rsquopasswordrsquo =gt rsquorsquo

rsquocharsetrsquo =gt rsquoutf8rsquo

rsquotablePrefixrsquo =gt rsquotbl rsquo

)

)

)

22 Scaffolding

Create read update and delete (CRUD) are the four basic operations of data objects in anapplication Because the task of implementing the CRUD operations is so common whendeveloping Web applications Yii provides a tool to automate this process (also known asscaffolding) In this section we will describe how to use the tool to implement CRUD forposts and comments

Open a command window and run the following commands

wwwrootyiiframeworkyiic shell wwwrootblogindexphp

Yii Interactive Tool v11

Please type rsquohelprsquo for help Type rsquoexitrsquo to quit

gtgt model

gtgt crud Post

gtgt crud Comment

gtgt exit

12 2 Initial Prototyping

Info Some PHP installations may use a different phpini file for command line(CLI) PHP parser As a result when running the above yiic commands youmay encounter errors like rdquoYiiBaseinclude(PDOphp) failed to open streamrdquoor rdquocould not find driverrdquo Please double check your CLI PHP configuration byexecuting the following command

php -r phpinfo()

The result of the above command will show which phpini file is being used andwhich extensions are loaded If a wrong phpini file is used you may use thefollowing command to explicitly specify the correct phpini to use

php -c phpini wwwrootyiiframeworkyiicphp shell wwwrootblogindexphp

The commands above accomplish two tasks First the model command generates a modelclass file for each table in the blog database Second the crud commands generate thecode needed by the CRUD operations for the Post and Comment models

Tip The command model generates a model class for every table in the databaseSometimes we probably donrsquot want to do so (eg the database contains some irrel-evant tables) In this case we can create model classes one by one For exampleto create the User model we can use the command model User The model com-mand also has some more advanced usages For more details execute help model

command

We can test the generated code by accessing the following URLs

httpwwwexamplecomblogindexphpr=post

httpwwwexamplecomblogindexphpr=comment

Notice that the post and comment features implemented by the generated code are com-pletely independent of each other Also when creating a new post or comment we arerequired to enter information such as author id and create time which in real applica-tion should be set by the program Donrsquot worry We will fix these problems in the nextmilestones For now we should be fairly satisfied as this prototype already contains mostfeatures that we need to implement for the blog application

To prepare for the next milestones letrsquos take a closer look at the files generated by theabove commands All the files are generated under wwwrootblogprotected For conve-nience we group them into model files controller files and view files

22 Scaffolding 13

bull model files

ndash modelsUserphp contains the User class that extends from CActiveRecord andcan be used to access the tbl user database table

ndash modelsPostphp contains the Post class that extends from CActiveRecord andcan be used to access the tbl post database table

ndash modelsTagphp contains the Tag class that extends from CActiveRecord andcan be used to access the tbl tag database table

ndash modelsCommentphp contains the Comment class that extends from CActiveRe-cord and can be used to access the tbl comment database table

ndash modelsLookupphp contains the Lookup class that extends from CActiveRecordand can be used to access the tbl lookup database table

bull controller file

ndash controllersPostControllerphp contains the PostController class which is thecontroller in charge of all CRUD operations about posts

ndash controllersCommentControllerphp contains the CommentController class whichis the controller in charge of all CRUD operations about comments

bull view files

ndash viewspostcreatephp is the view file that shows an HTML form to create anew post

ndash viewspostupdatephp is the view file that shows an HTML form to update anexisting post

ndash viewspostviewphp is the view file that displays the detailed information of apost

ndash viewspostindexphp is the view file that displays a list of posts

ndash viewspostadminphp is the view file that displays posts in a table with ad-ministrative commands

ndash viewspost formphp is the partial view file embedded in viewspostcreate

php and viewspostupdatephp It displays the HTML form for collecting postinformation

ndash viewspost viewphp is the partial view file used by viewspostindexphp Itdisplays the brief view of a single post

ndash a similar set of view files are also generated for comment

14 2 Initial Prototyping

In order to understand better how the above files are used we show in the following theworkflow that occurs in the blog application when displaying a list of posts

1 The user requests the URL httpwwwexamplecomblogindexphpr=posts

2 The entry script is executed by the Web server which creates and initializes anapplication instance to handle the request

3 The application creates an instance of PostController and executes it

4 The PostController instance executes the index action by calling its actionIndex()

method Note that index is the default action if the user does not specify an actionto execute in the URL

5 The actionIndex() method queries database to bring back the list of recent posts

6 The actionIndex() method renders the index view with the post data

23 Authenticating User

Our blog application needs to differentiate between the system owner and guest usersTherefore we need to implement the user authentication feature

As you may have found that the skeleton application already provides user authenticationby checking if the username and password are both demo or admin In this section wewill modify the corresponding code so that the authentication is done against the User

database table

User authentication is performed in a class implementing the IUserIdentity interface Theskeleton application uses the UserIdentity class for this purpose The class is stored inthe file wwwrootblogprotectedcomponentsUserIdentityphp

Tip By convention the name of a class file must be the same as the correspondingclass name suffixed with the extension php Following this convention one canrefer to a class using a path alias For example we can refer to the UserIdentity

class with the alias applicationcomponentsUserIdentity Many APIs in Yiican recognize path aliases (eg YiicreateComponent()) and using path aliasesavoids the necessity of embedding absolute file paths in the code The existence ofthe latter often causes trouble when we deploy an application

We modify the UserIdentity class as follows

23 Authenticating User 15

ltphp

class UserIdentity extends CUserIdentity

private $ id

public function authenticate()

$username=strtolower($this-gtusername)

$user=Usermodel()-gtfind(rsquoLOWER(username)=rsquoarray($username))

if($user===null)

$this-gterrorCode=selfERROR USERNAME INVALID

else if($user-gtvalidatePassword($this-gtpassword))

$this-gterrorCode=selfERROR PASSWORD INVALID

else

$this-gt id=$user-gtid

$this-gtusername=$user-gtusername

$this-gterrorCode=selfERROR NONE

return $this-gterrorCode==selfERROR NONE

public function getId()

return $this-gt id

In the authenticate() method we use the User class to look for a row in the User tablewhose username column is the same as the given username in a case-insensitive mannerRemember that the User class was created using the yiic tool in the prior section Becausethe User class extends from CActiveRecord we can exploit the ActiveRecord feature toaccess the User table in an OOP fashion

In order to check if the user has entered a valid password we invoke the validatePassword

method of the User class We need to modify the file wwwrootblogprotectedmodels

Userphp as follows Note that instead of storing the plain password in the database westore the hash result of the password and a randomly generated salt key When validatingthe user-entered password we should compare the hash results instead

class User extends CActiveRecord

public function validatePassword($password)

return $this-gthashPassword($password$this-gtsalt)===$this-gtpassword

16 2 Initial Prototyping

public function hashPassword($password$salt)

return md5($salt$password)

In the UserIdentity class we also override the getId() method which returns the id valueof the user found in the User table The parent implementation would return the usernameinstead Both the username and id properties will be stored in the user session and maybe accessed via Yiiapp()-gtuser from anywhere in our code

Tip In the UserIdentity class we reference the class CUserIdentity without ex-plicitly including the corresponding class file This is because CUserIdentity is acore class provided by the Yii framework Yii will automatically include the classfile for any core class when it is referenced for the first time

We also do the same with the User class This is because the User class file is placedunder the directory wwwrootblogprotectedmodels which has been added tothe PHP include path according to the following lines found in the applicationconfiguration

return array(

rsquoimportrsquo=gtarray(

rsquoapplicationmodelsrsquo

rsquoapplicationcomponentsrsquo

)

)

The above configuration says that any class whose class file is located under either wwwrootblogprotectedmodels or wwwrootblogprotectedcomponents willbe automatically included when the class is referenced for the first time

The UserIdentity class is mainly used by the LoginForm class to authenticate a user basedon the username and password input collected from the login page The following codefragment shows how UserIdentity is used

$identity=new UserIdentity($username$password)

$identity-gtauthenticate()

switch($identity-gterrorCode)

24 Summary 17

case UserIdentityERROR NONE

Yiiapp()-gtuser-gtlogin($identity)

break

Info People often get confused about identity and the user application componentThe former represents a way of performing authentication while the latter is usedto represent the information related with the current user An application can onlyhave one user component but it can have one or several identity classes dependingon what kind of authentication it supports Once authenticated an identity instancemay pass its state information to the user component so that they are globallyaccessible via user

To test the modified UserIdentity class we can browse the URL httpwwwexample

comblogindexphp and try logging in with the username and password that we store inthe User table If we use the database provided by the blog demo we should be able tologin with username demo and password demo Note that this blog system does not providethe user management feature As a result a user cannot change his account or create anew one through the Web interface The user management feature may be considered asa future enhancement to the blog application

24 Summary

We have completed the milestone 1 Letrsquos summarize what we have done so far

1 We identified the requirements to be fulfilled

2 We installed the Yii framework

3 We created a skeleton application

4 We designed and created the blog database

5 We modified the application configuration by adding the database connection

6 We generated the code that implements the basic CRUD operations for both postsand comments

7 We modified the authentication method to check against the tbl user table

18 2 Initial Prototyping

For a new project most of the time will be spent in step 1 and 4 for this first milestone

Although the code generated by the yiic tool implements fully functional CRUD oper-ations for a database table it often needs to be modified in practical applications Forthis reason in the next two milestone our job is to customize the generated CRUD codeabout posts and comments so that it reaches our initial requirements

In general we first modify the model class file by adding appropriate validation rules anddeclaring relational objects We then modify the controller action and view code for eachindividual CRUD operation

Chapter 3

Post Management

31 Customizing Post Model

The Post model class generated by the yiic tool mainly needs to be modified in two places

bull the rules() method specifies the validation rules for the model attributes

bull the relations() method specifies the related objects

Info A model consists of a list of attributes each associated with a column in thecorresponding database table Attributes can be declared explicitly as class membervariables or implicitly without any declaration

311 Customizing rules() Method

We first specify the validation rules which ensure the attribute values entered by usersare correct before they are saved to the database For example the status attribute ofPost should be an integer 1 2 or 3 The yiic tool also generates validation rules for eachmodel However these rules are based on the table column information and may not beappropriate

Based on the requirement analysis we modify the rules() method as follows

public function rules()

return array(

array(rsquotitle content statusrsquo rsquorequiredrsquo)

array(rsquotitlersquo rsquolengthrsquo rsquomaxrsquo=gt128)

array(rsquostatusrsquo rsquoinrsquo rsquorangersquo=gtarray(123))

array(rsquotagsrsquo rsquomatchrsquo rsquopatternrsquo=gtrsquo^[ws]+$rsquorsquomessagersquo=gtrsquoTags can only contain word charactersrsquo)

20 3 Post Management

array(rsquotagsrsquo rsquonormalizeTagsrsquo)

)

In the above we specify that the title content and status attributes are required thelength of title should not exceed 128 the status attribute value should be 1 (draft) 2(published) or 3 (archived) and the tags attribute should only contain word charactersand commas In addition we use normalizeTags to normalize the user-entered tags so thatthe tags are unique and properly separated with commas

The validators such as required length in and match are all built-in validators providedby Yii The normalizeTags validator is a method-based validator that we need to definein the Post class For more information about how to specify validation rules please referto the Guide

public function normalizeTags($attribute$params)

$this-gttags=Tagarray2string(array unique(Tagstring2array($this-gttags)))

The rules declared in this method are executed one by one when we call the validate() orsave() method of the model instance

Note It is very important to remember that attributes appearing in rules() mustbe those to be entered by end users Other attributes such as id and create time

in the Post model which are set by our code or database should not be in rules()For more details please refer to Securing Attribute Assignments

After making these changes we can visit the post creation page again to verify that thenew validation rules are taking effect

312 Customizing relations() Method

Lastly we customize the relations() method to specify the related objects of a post Bydeclaring these related objects in relations() we can exploit the powerful RelationalActiveRecord (RAR) feature to access the related object information of a post such asits author and comments without the need to write complex SQL JOIN statements

We customize the relations() method as follows

31 Customizing Post Model 21

public function relations()

return array(

rsquoauthorrsquo =gt array(selfBELONGS TO rsquoUserrsquo rsquoauthor idrsquo)

rsquocommentsrsquo =gt array(selfHAS MANY rsquoCommentrsquo rsquopost idrsquo

rsquoconditionrsquo=gtrsquocommentsstatus=rsquoCommentSTATUS APPROVED

rsquoorderrsquo=gtrsquocommentscreate time DESCrsquo)

rsquocommentCountrsquo =gt array(selfSTAT rsquoCommentrsquo rsquopost idrsquo

rsquoconditionrsquo=gtrsquostatus=rsquoCommentSTATUS APPROVED)

)

The above relations state that

bull A post belongs to an author whose class is User and the relationship is establishedbased on the author id attribute value of the post

bull A post has many comments whose class is Comment and the relationship is establishedbased on the post id attribute value of the comments These comments should besorted according to their creation time and the comments must be approved

bull The commentCount relation is a bit special as it returns back an aggregation resultwhich is about how many comments the post has

With the above relation declaration we can easily access the author and comments of apost like the following

$author=$post-gtauthor

echo $author-gtusername

$comments=$post-gtcomments

foreach($comments as $comment)

echo $comment-gtcontent

For more details about how to declare and use relations please refer to the Guide

313 Representing Status in Text

Because the status of a post is stored as an integer in the database we need to provide atextual representation so that it is more intuitive when being displayed to end users In alarge system the similar requirement is very common

22 3 Post Management

As a generic solution we use the tbl lookup table to store the mapping between integervalues and textual representations that are needed by other data objects We modify theLookup model class as follows to more easily access the textual data in the table

class Lookup extends CActiveRecord

private static $ items=array()

public static function items($type)

if(isset(self$ items[$type]))

selfloadItems($type)

return self$ items[$type]

public static function item($type$code)

if(isset(self$ items[$type]))

selfloadItems($type)

return isset(self$ items[$type][$code]) self$ items[$type][$code] false

private static function loadItems($type)

self$ items[$type]=array()

$models=selfmodel()-gtfindAll(array(

rsquoconditionrsquo=gtrsquotype=typersquo

rsquoparamsrsquo=gtarray(rsquotypersquo=gt$type)

rsquoorderrsquo=gtrsquopositionrsquo

))

foreach($models as $model)

self$ items[$type][$model-gtcode]=$model-gtname

Our new code mainly provides two static methods Lookupitems() and Lookupitem()The former returns a list of strings belonging to the specified data type while the latterreturns a particular string for the given data type and data value

Our blog database is pre-populated with two lookup types PostStatus and CommentStatusThe former refers to the possible post statuses while the latter the comment statuses

In order to make our code easier to read we also declare a set of constants to representthe status integer values We should use these constants through our code when referringto the corresponding status values

32 Creating and Updating Posts 23

class Post extends CActiveRecord

const STATUS DRAFT=0

const STATUS PUBLISHED=1

const STATUS ARCHIVED=2

Therefore we can call Lookupitems(rsquoPostStatusrsquo) to get the list of possible post statuses(text strings indexed by the corresponding integer values) and call Lookupitem(rsquoPostStatusrsquoPostSTATUS PUBLISHED) to get the string representation of the published status

32 Creating and Updating Posts

With the Post model ready we need to fine-tune the actions and views for the controllerPostController In this section we first customize the access control of CRUD operationswe then modify the code implementing the create and update operations

321 Customizing Access Control

The first thing we want to do is to customize the access control because the code generatedby yiic does not fit our needs

We modify the accessRules() method in the file wwwrootblogprotectedcontrollers

PostControllerphp as follows

public function accessRules()

return array(

array(rsquoallowrsquo allow all users to perform rsquolistrsquo and rsquoshowrsquo actions

rsquoactionsrsquo=gtarray(rsquoindexrsquo rsquoviewrsquo)

rsquousersrsquo=gtarray(rsquorsquo)

)

array(rsquoallowrsquo allow authenticated users to perform any action

rsquousersrsquo=gtarray(rsquorsquo)

)

array(rsquodenyrsquo deny all users

rsquousersrsquo=gtarray(rsquorsquo)

)

)

The above rules state that all users can access the index and view actions and authenti-cated users can access any actions including the admin action The user should be denied

24 3 Post Management

access in any other scenario Note that these rules are evaluated in the order they arelisted here The first rule matching the current context makes the access decision Forexample if the current user is the system owner who tries to visit the post creation pagethe second rule will match and it will give the access to the user

322 Customizing create and update Operations

The create and update operations are very similar They both need to display an HTMLform to collect user inputs validate them and save them into database The main differ-ence is that the update operation will pre-populate the form with the existing post datafound in the database For this reason the yiic tool generates a partial view wwwroot

blogprotectedviewspost formphp that is embedded in both the create and update

views to render the needed HTML form

We first change the formphp file so that the HTML form only collects the inputs wewant title content tags and status We use plain text fields to collect inputs for thefirst three attributes and a dropdown list to collect input for status The dropdown listoptions are the text displays of the possible post statuses

ltphp echo CHtmlactiveDropDownList($postrsquostatusrsquoLookupitems(rsquoPostStatusrsquo)) gt

In the above we call Lookupitems(rsquoPostStatusrsquo) to bring back the list of post statuses

We then modify the Post class so that it can automatically set some attributes (eg create

time author id) before a post is saved to the database We override the beforeSave()

method as follows

protected function beforeSave()

if(parentbeforeSave())

if($this-gtisNewRecord)

$this-gtcreate time=$this-gtupdate time=time()

$this-gtauthor id=Yiiapp()-gtuser-gtid

else

$this-gtupdate time=time()

return true

else

return false

33 Displaying Posts 25

When we save a post we want to update the tbl tag table to reflect the change of tagfrequencies We can do this work in the afterSave() method which is automaticallyinvoked by Yii after a post is successfully saved into the database

protected function afterSave()

parentafterSave()

Tagmodel()-gtupdateFrequency($this-gt oldTags $this-gttags)

private $ oldTags

protected function afterFind()

parentafterFind()

$this-gt oldTags=$this-gttags

In the implementation because we want to detect if the user changes the tags in case he isupdating an existing post we need to know what the old tags are For this reason we alsowrite the afterFind() method to keep the old tags in the variable oldTags The methodafterFind() is invoked automatically by Yii when an AR record is populated with thedata from database

We are not going to give details of the TagupdateFrequency() method here Interestedreaders may refer to the file wwwrootyiidemosblogprotectedmodelsTagphp

33 Displaying Posts

In our blog application a post may be displayed among a list of posts or by itself Theformer is implemented as the index operation while the latter the view operation In thissection we customize both operations to fulfill our initial requirements

331 Customizing view Operation

The view operation is implemented by the actionView() method in PostController Itsdisplay is generated by the view view with the view file wwwrootblogprotectedviews

postviewphp

Below is the relevant code implementing the view operation in PostController

public function actionView()

26 3 Post Management

$this-gtrender(rsquoviewrsquoarray(

$post=$this-gtloadModel()

$this-gtrender(rsquoviewrsquoarray(

rsquomodelrsquo=gt$post

))

))

private $ model

public function loadModel()

if($this-gt model===null)

if(isset($ GET[rsquoidrsquo]))

if(Yiiapp()-gtuser-gtisGuest)

$condition=rsquostatus=rsquoPostSTATUS PUBLISHED

rsquo OR status=rsquoPOSTSTATUS ARCHIVED

else

$condition=rsquorsquo

$this-gt model=Postmodel()-gtfindbyPk($ GET[rsquoidrsquo] $condition)

if($this-gt model===null)

throw new CHttpException(404rsquoThe requested page does not existrsquo)

return $this-gt model

Our change mainly lies in the loadModel() method In this method we query the Post

table according to the id GET parameter If the post is not found or if it is not publishedor archived (when the user is a guest) we will throw a 404 HTTP error Otherwise thepost object is returned to actionView() which in turn passes the post object to the viewscript for further display

Tip Yii captures HTTP exceptions (instances of CHttpException) and displaysthem in either predefined templates or customized error views The skeleton appli-cation generated by yiic already contains a customized error view in wwwroot

blogprotectedviewssiteerrorphp We can modify this file if we want tofurther customize the error display

The change in the view script is mainly about ajdusting the formatting and styles of thepost display We will not go into details here Interested readers may refer to wwwroot

blogprotectedviewspostviewphp

33 Displaying Posts 27

332 Customizing index Operation

Like the view operation we customize the index operation in two places the actionIndex()

method in PostController and the view file wwwrootblogprotectedviewspostindex

php We mainly need to add the support for displaying a list of posts that are associatedwith a specified tag

Below is the modified actionIndex() method in PostController

public function actionIndex()

$criteria=new CDbCriteria(array(

rsquoconditionrsquo=gtrsquostatus=rsquoPostSTATUS PUBLISHED

rsquoorderrsquo=gtrsquoupdate time DESCrsquo

rsquowithrsquo=gtrsquocommentCountrsquo

))

if(isset($ GET[rsquotagrsquo]))

$criteria-gtaddSearchCondition(rsquotagsrsquo$ GET[rsquotagrsquo])

$dataProvider=new CActiveDataProvider(rsquoPostrsquo array(

rsquopaginationrsquo=gtarray(

rsquopageSizersquo=gtYiiapp()-gtparams[rsquopostsPerPagersquo]

)

rsquocriteriarsquo=gt$criteria

))

$this-gtrender(rsquoindexrsquoarray(

rsquodataProviderrsquo=gt$dataProvider

))

In the above we first create a query criteria for retrieving post list The criteria statesthat only published posts should be returned and they should be sorted according to theirupdate time in descending order Because when displaying a post in the list we want toshow how many comments the post has received in the criteria we also specify to bringback commentCount which if you remember is a relation declared in Postrelations()

In case when a user wants to see posts with a specific tag we would add a search conditionto the criteria to look for the specified tag

Using the query criteria we create a data provider which mainly serves for three purposesFirst it does pagination of the data when too many results may be returned Herewe customize the pagination by setting the page size to be an application parameterpostsPerPage Second it does sorting according to the user request And finally it feedsthe paginated and sorted data to widgets or view code for presentation

28 3 Post Management

After we finish with actionIndex() we modify the index view as follows Our change ismainly about adding the h1 header when the user specifies to display posts with a tag

ltphp if(empty($ GET[rsquotagrsquo])) gt

lth1gtPosts Tagged with ltigtltphp echo CHtmlencode($ GET[rsquotagrsquo]) gtltigtlth1gt

ltphp endif gt

ltphp $this-gtwidget(rsquoziiwidgetsCListViewrsquo array(

rsquodataProviderrsquo=gt$dataProvider

rsquoitemViewrsquo=gtrsquo viewrsquo

rsquotemplatersquo=gtitemsnpager)) gt

Note that in the above we use [CListView] to display the post list This widget requires apartial view to display the detail of each individual post Here we specify the partial viewto be view which means the file wwwrootblogprotectedviewspost viewphp In thisview script we can acccess the post instance being displayed via a local variable named$data

34 Managing Posts

Managing posts mainly refers to listing posts in an administrative view that allows us tosee posts with all statuses updating them and deleting them They are accomplished bythe admin operation and the delete operation respectively The code generated by yiic

does not need much modification Below we mainly explain how these two operations areimplemented

341 Listing Posts in Tabular View

The admin operation shows posts with all statuses in a tabular view The view supportssorting and pagination The following is the actionAdmin() method in PostController

public function actionAdmin()

$dataProvider=new CActiveDataProvider(rsquoPostrsquo array(

rsquosortrsquo=gtarray(

rsquodefaultOrderrsquo=gtrsquostatus update time DESCrsquo

)

))

$this-gtrender(rsquoadminrsquoarray(

rsquodataProviderrsquo=gt$dataProvider

))

34 Managing Posts 29

The above code is very similar to that in actionIndex() The main difference is in thequery criteria Here we do not specify any particular query criteria since we want to seeall posts We do customize the sort property of the data provider so that by default theposts are shown in the order of their statuses and update time

Below is the code for the admin view

lth1gtManage Postslth1gt

ltphp $this-gtwidget(rsquoziiwidgetsgridCGridViewrsquo array(

rsquodataProviderrsquo=gt$dataProvider

rsquocolumnsrsquo=gtarray(

array(

rsquonamersquo=gtrsquotitlersquo

rsquotypersquo=gtrsquorawrsquo

rsquovaluersquo=gtrsquoCHtmllink(CHtmlencode($data-gttitle) $data-gturl)rsquo

)

array(

rsquonamersquo=gtrsquostatusrsquo

rsquovaluersquo=gtrsquoLookupitem(PostStatus$data-gtstatus)rsquo

)

rsquocreate timedatetimersquo

rsquoupdate timedatetimersquo

array(

rsquoclassrsquo=gtrsquoCButtonColumnrsquo

)

)

)) gt

We use [CGridView] to display the posts It allows us to sort by a column and paginatethrough the posts if there are too many to be displayed in a single page Our change ismainly about how to display each column For example for the title column we specifythat it should be displayed as a hyperlink that points to the detailed view of the post

Tip When displaying text we call CHtmlencode() to encode HTML entities init This prevents from cross-site scripting attack

342 Deleting Posts

In the admin data grid there is a delete button in each row Clicking on the button shoulddelete the corresponding post Internally this triggers the delete action implemented asfollows

30 3 Post Management

public function actionDelete()

if(Yiiapp()-gtrequest-gtisPostRequest)

we only allow deletion via POST request

$this-gtloadModel()-gtdelete()

if(isset($ POST[rsquoajaxrsquo]))

$this-gtredirect(array(rsquoindexrsquo))

else

throw new CHttpException(400rsquoInvalid request Please do not repeat this request againrsquo)

The above code is the one generated by the yiic tool without any change We wouldlike to explain a little bit more about the checking on $ POST[rsquoajaxrsquo] The [CGridView]widget has a very nice feature that its sorting pagination and deletion operations are alldone in AJAX mode by default That means the whole page does not get reloaded if anyof the above operations is performed However it is also possible that the widget runs innon-AJAX mode (by setting its ajaxUpdate property to be false or disabling JavaScript onthe client side) It is necessary for the delete action to differentiate these two scenarios ifthe delete request is made via AJAX we should not redirect the user browser otherwisewe should

Deleting a post should also cause the deletion of all comments for that post In additionwe should also update the tbl tag table regarding the tags for the deleted post Both ofthese tasks can be achieved by writing an afterDelete method in the Post model class asfollows

protected function afterDelete()

parentafterDelete()

Commentmodel()-gtdeleteAll(rsquopost id=rsquo$this-gtid)

Tagmodel()-gtupdateFrequency($this-gttags rsquorsquo)

The above code is very straightforward it first deletes all those comments whose post id

is the same as the ID of the deleted post it then updates the tbl tag table for the tags

of the deleted post

34 Managing Posts 31

Tip We have to explicitly delete all comments for the deleted post here becauseSQLite does not really support foreign key constraints In a DBMS that supportsthis constraint (such as MySQL PostgreSQL) the foreign key constraint can be setup such that the DBMS automatically deletes the related comments if the post isdeleted In that case we no longer this explicit deletion call in our code

32 3 Post Management

Chapter 4

Comment Management

41 Customizing Comment Model

For the Comment model we mainly need to customize the rules() and attributeLabels()

methods The attributeLabels() method returns a mapping between attribute namesand attribute labels We do not need to touch relations() since the code generated bythe yiic tool is good enough

411 Customizing rules() Method

We first customize the validation rules generated by the yiic tool The following rules areused for comments

public function rules()

return array(

array(rsquocontent author emailrsquo rsquorequiredrsquo)

array(rsquoauthor email urlrsquo rsquolengthrsquo rsquomaxrsquo=gt128)

array(rsquoemailrsquorsquoemailrsquo)

array(rsquourlrsquorsquourlrsquo)

)

In the above we specify that the author email and content attributes are required thelength of author email and url cannot exceed 128 the email attribute must be a validemail address and the url attribute must be a valid URL

412 Customizing attributeLabels() Method

We then customize the attributeLabels() method to declare the label display for eachmodel attribute This method returns an array consisting of name-label pairs When wecall CHtmlactiveLabel() to display an attribute label

34 4 Comment Management

public function attributeLabels()

return array(

rsquoidrsquo =gt rsquoIdrsquo

rsquocontentrsquo =gt rsquoCommentrsquo

rsquostatusrsquo =gt rsquoStatusrsquo

rsquocreate timersquo =gt rsquoCreate Timersquo

rsquoauthorrsquo =gt rsquoNamersquo

rsquoemailrsquo =gt rsquoEmailrsquo

rsquourlrsquo =gt rsquoWebsitersquo

rsquopost idrsquo =gt rsquoPostrsquo

)

Tip If the label for an attribute is not declared in attributeLabels() an algo-rithm will be used to generate an appropriate label For example a label VerifyCode will be generated for attributes verify code or verifyCode

413 Customizing Saving Process

Because we want to record the creation time of a comment we override the beforeSave()

method of Comment like we do for the Post model

protected function beforeSave()

if(parentbeforeSave())

if($this-gtisNewRecord)

$this-gtcreate time=time()

return true

else

return false

42 Creating and Displaying Comments

In this section we implement the comment display and creation features

421 Displaying Comments

Instead of displaying and creating comments on individual pages we use the post detailpage (generated by the view action of PostController) Below the post content display

42 Creating and Displaying Comments 35

we display first a list of comments belonging to that post and then a comment creationform

In order to display comments on the post detail page we modify the view script wwwrootblogprotectedviewspostviewphp as follows

ltdiv id=commentsgt

ltphp if($model-gtcommentCountgt=1) gt

lth3gt

ltphp echo $model-gtcommentCount rsquocomment(s)rsquo gt

lth3gt

ltphp $this-gtrenderPartial(rsquo commentsrsquoarray(

rsquopostrsquo=gt$model

rsquocommentsrsquo=gt$model-gtcomments

)) gt

ltphp endif gt

ltdivgt

In the above we call renderPartial() to render a partial view named comments to displaythe list of comments belonging to the current post Note that in the view we use theexpression $model-gtcomments to retrieve the comments for the post This is valid becausewe have declared a comments relation in the Post class Evaluating this expression wouldtrigger an implicit JOIN database query to bring back the proper comments This featureis known as lazy relational query

The partial view comments is not very interesting It mainly goes through every commentand displays the detail of it Interested readers may refer to wwwrootyiidemosblog

protectedpost commentsphp

422 Creating Comments

To handle comment creation we first modify the actionView() method of PostController

as follows

public function actionView()

$post=$this-gtloadModel()

$comment=$this-gtnewComment($post)

$this-gtrender(rsquoviewrsquoarray(

rsquomodelrsquo=gt$post

rsquocommentrsquo=gt$comment

))

36 4 Comment Management

protected function newComment($post)

$comment=new Comment

if(isset($ POST[rsquoCommentrsquo]))

$comment-gtattributes=$ POST[rsquoCommentrsquo]

if($post-gtaddComment($comment))

if($comment-gtstatus==CommentSTATUS PENDING)

Yiiapp()-gtuser-gtsetFlash(rsquocommentSubmittedrsquorsquoThank yoursquo)

$this-gtrefresh()

return $comment

In the above we call the newComment() method before we render view In the newComment()

method we generate a Comment instance and check if the comment form is submitted If sowe try to add the comment for the post by calling $post-gtaddComment($comment) If it goesthrough we refresh the post detail page In case the comment needs to be approved we willshow a flash message to indicate this decision A flash message is usually a confirmationmessage displayed to end users If the user clicks on the refresh button of his browser themessage will disappear

We also need to modify wwwrootblogprotectedviewspostviewphp furthermore

ltdiv id=commentsgt

lth3gtLeave a Commentlth3gt

ltphp if(Yiiapp()-gtuser-gthasFlash(rsquocommentSubmittedrsquo)) gt

ltdiv class=successgt

ltphp echo Yiiapp()-gtuser-gtgetFlash(rsquocommentSubmittedrsquo) gt

ltdivgt

ltphp else gt

ltphp $this-gtrenderPartial(rsquocomment formrsquoarray(

rsquomodelrsquo=gt$comment

)) gt

ltphp endif gt

ltdivgtlt-- comments --gt

43 Managing Comments 37

In the above code we display the flash message if it is available If not we displaythe comment input form by rendering the partial view wwwrootblogprotectedviews

comment formphp

43 Managing Comments

Comment management includes updating deleting and approving comments These op-erations are implemented as actions in the CommentController class

431 Updating and Deleting Comments

The code generated by yiic for updating and deleting comments remains largely un-changed

432 Approving Comments

When comments are newly created they are in pending approval status and need to beapproved in order to be visible to guest users Approving a comment is mainly aboutchanging the status column of the comment

We create an actionApprove() method in CommentController as follows

public function actionApprove()

if(Yiiapp()-gtrequest-gtisPostRequest)

$comment=$this-gtloadModel()

$comment-gtapprove()

$this-gtredirect(array(rsquoindexrsquo))

else

throw new CHttpException(400rsquoInvalid requestrsquo)

In the above when the approve action is invoked via a POST request we call the approve()

method defined in the Comment model to change the status We then redirect the userbrowser to the page displaying the post that this comment belongs to

We also modify the actionIndex() method of Comment to show all comments We wouldlike to see comments pending approved to show up first

public function actionIndex()

38 4 Comment Management

$dataProvider=new CActiveDataProvider(rsquoCommentrsquo array(

rsquocriteriarsquo=gtarray(

rsquowithrsquo=gtrsquopostrsquo

rsquoorderrsquo=gtrsquotstatus tcreate time DESCrsquo

)

))

$this-gtrender(rsquoindexrsquoarray(

rsquodataProviderrsquo=gt$dataProvider

))

Notice that in the above code because both tbl post and tbl comment have columns statusand create time we need to disambiguate the corresponding column reference by prefixingthem with table alias names As described in the guide the alias for the primary table ina relational query is always t Therefore we are prefixing t to the status and create time

columns in the above code

Like the post index view the index view for CommentController uses [CListView] to displaythe comment list which in turn uses the partial view wwwrootblogprotectedviews

comment viewphp to display the detail of each individual comment We will not go intodetails here Interested readers may refer to the corresponding file in the blog demowwwrootyiidemosblogprotectedviewscomment viewphp

Chapter 5

Portlets

51 Customizing Page Layout

Features like rdquothe most recent commentsrdquo rdquotag cloudrdquo are better to be implemented inportlets A portlet is a pluggable user interface component that renders a fragment ofHTML code In this section we describe how to set up the portlet architecture for ourblog application

Before we go into details about developing portlets we first need to adjust our page layoutso that the portlets can be placed in a side bar

A page layout determines how to share common sections (eg header footer) amongdifferent pages and how to embed contents that are specific for different pages In Yiia page layout is represented as a view under the protectedviewslayouts folder Whenwe call the render() method in a controller the rendering result will be automaticallyembedded into this layout view before it is displayed to end users The default layoutview is named as main which means the wwwrootblogprotectedviewslayoutsmain

php file in our blog application

We could directly modify the main layout to add a side bar However doing so wouldmake some irrelevant pages such as about contact pages to have a side bar which is notalways desirable For this reason we need different layouts for different pages

We thus create two different layouts column1 and column2 The former displays the pagein a single column while the latter displays two columns in the page one of which is theside bar We will use column2 layout for the post and comment pages and column1 forthe rest of the pages Because both column1 and column2 still share the same header andfooter sections we let them to share the main layout

Based on the above description we create the column1 layout as follows

ltphp $this-gtbeginContent(rsquolayoutsmainrsquo) gt

ltdiv class=containergt

40 5 Portlets

ltdiv id=contentgt

ltphp echo $content gt

ltdivgtlt-- content --gt

ltdivgt

ltphp $this-gtendContent() gt

and the column2 layout as follows

ltphp $this-gtbeginContent(rsquolayoutsmainrsquo) gt

ltdiv class=containergt

ltdiv class=span-18gt

ltdiv id=contentgt

ltphp echo $content gt

ltdivgtlt-- content --gt

ltdivgt

ltdiv class=span-6 lastgt

ltdiv id=sidebargt

portlets to be placed here

ltdivgtlt-- sidebar --gt

ltdivgt

ltdivgt

ltphp $this-gtendContent() gt

As we can see both column1 and column2 are enclosed in a pair of beginContent andendContent calls They essentially use the specified view layoutsmain to decorate theenclosed content In all these layout views the variable $content refers to the dynamiccontent to be embedded into the layout

Besides customizing the layout view file we also need to adjust the CSS file wwwroot

blogcssmaincss so that the overall appearance would look like what we see in the blogdemo We will not go into details here

Info The skeleton application generated by the yiic tool uses the BluePrint CSSframework It provides a nice font reset and a robust grid system For more detailson how to use this framework please refer to its documentation

After creating the new layouts we need to set the layout property of controllers to makeuse of them So far we have three controllers SiteController PostController andCommentController We set SiteController to use column1 as the default layout whilethe rest two of the controllers use column2 for example

class PostController extends Controller

52 Creating User Menu Portlet 41

public $layout=rsquocolumn2rsquo

Tip If some view of the controller does not use the same layout as declaredby the layout property it may customize it in the view by a simple assignment$this-gtlayout=rsquonewlayoutrsquo

52 Creating User Menu Portlet

Based on the requirements analysis we need three different portlets the rdquouser menurdquoportlet the rdquotag cloudrdquo portlet and the rdquorecent commentsrdquo portlet We will develop theseportlets by extending the [CPortlet] widget provided by Yii

In this section we will develop our first concrete portlet - the user menu portlet whichdisplays a list of menu items that are only available to authenticated users The menucontains four items

bull Approve Comments a hyperlink that leads to a list of comments pending approval

bull Create New Post a hyperlink that leads to the post creation page

bull Manage Posts a hyperlink that leads to the post management page

bull Logout a link button that would log out the current user

521 Creating UserMenu Class

We create the UserMenu class to represent the logic part of the user menu portlet Theclass is saved in the file wwwrootblogprotectedcomponentsUserMenuphp which has thefollowing content

Yiiimport(rsquoziiwidgetsCPortletrsquo)

class UserMenu extends CPortlet

public function init()

$this-gttitle=CHtmlencode(Yiiapp()-gtuser-gtname)

parentinit()

42 5 Portlets

protected function renderContent()

$this-gtrender(rsquouserMenursquo)

The UserMenu class extends from the CPortlet class that we created previously It overridesboth the init() method and the renderContent() method of CPortlet The former setsthe portlet title to be the name of the current user the latter generates the portlet bodycontent by rendering a view named userMenu

Tip Notice that we have to explicitly include the CPortlet class by calling Yii

import() before we refer to it the first time This is because CPortlet is part of thezii project ndash the official extension library for Yii For performance considerationclasses in this project are not listed as core classes Therefore we have to import itbefore we use it the first time

522 Creating userMenu View

Next we create the userMenu view which is saved in the file wwwrootblogprotected

componentsviewsuserMenuphp

ltulgt

ltligtltphp echo CHtmllink(rsquoCreate New Postrsquoarray(rsquopostcreatersquo)) gtltligt

ltligtltphp echo CHtmllink(rsquoManage Postsrsquoarray(rsquopostadminrsquo)) gtltligt

ltligtltphp echo CHtmllink(rsquoApprove Commentsrsquoarray(rsquocommentindexrsquo))

rsquo (rsquo Commentmodel()-gtpendingCommentCount rsquo)rsquo gtltligt

ltligtltphp echo CHtmllink(rsquoLogoutrsquoarray(rsquositelogoutrsquo)) gtltligt

ltulgt

Info By default view files for a widget should be placed under the views sub-directory of the directory containing the widget class file The file name must bethe same as the view name

523 Using UserMenu Portlet

It is time for us to make use of our newly completed UserMenu portlet We modify thelayout view file wwwrootblogprotectedviewslayoutscolumn2php as follows

53 Creating Tag Cloud Portlet 43

ltdiv id=sidebargt

ltphp if(Yiiapp()-gtuser-gtisGuest) $this-gtwidget(rsquoUserMenursquo) gt

ltdivgt

In the above we call the widget() method to generate and execute an instance of theUserMenu class Because the portlet should only be displayed to authenticated users weonly call widget() when the isGuest property of the current user is false (meaning theuser is authenticated)

524 Testing UserMenu Portlet

Letrsquos test what we have so far

1 Open a browser window and enter the URL httpwwwexamplecomblogindex

php Verify that there is nothing displayed in the side bar section of the page

2 Click on the Login hyperlink and fill out the login form to login If successful verifythat the UserMenu portlet appears in the side bar and the portlet has the usernameas its title

3 Click on the rsquoLogoutrsquo hyperlink in the UserMenu portlet Verify that the logout actionis successful and the UserMenu portlet disappears

525 Summary

What we have created is a portlet that is highly reusable We can easily reuse it in adifferent project with little or no modification Moreover the design of this portlet followsclosely the philosophy that logic and presentation should be separated While we did notpoint this out in the previous sections such practice is used nearly everywhere in a typicalYii application

53 Creating Tag Cloud Portlet

Tag cloud displays a list of post tags with visual decorations hinting the popularity of eachindividual tag

531 Creating TagCloud Class

We create the TagCloud class in the file wwwrootblogprotectedcomponentsTagCloud

php The file has the following content

44 5 Portlets

Yiiimport(rsquoziiwidgetsCPortletrsquo)

class TagCloud extends CPortlet

public $title=rsquoTagsrsquo

public $maxTags=20

protected function renderContent()

$tags=Tagmodel()-gtfindTagWeights($this-gtmaxTags)

foreach($tags as $tag=gt$weight)

$link=CHtmllink(CHtmlencode($tag) array(rsquopostindexrsquorsquotagrsquo=gt$tag))

echo CHtmltag(rsquospanrsquo array(

rsquoclassrsquo=gtrsquotagrsquo

rsquostylersquo=gtfont-size$weightpt) $link)n

Unlike the UserMenu portlet the TagCloud portlet does not use a view Instead its presen-tation is done in the renderContent() method This is because the presentation does notcontain much HTML tags

We display each tag as a hyperlink to the post index page with the corresponding tagparameter The font size of each tag link is adjusted according to their relative weightamong other tags If a tag has higher frequency value than the other it will have a biggerfont size

532 Using TagCloud Portlet

Usage of the TagCloud portlet is very simple We modify the layout file wwwrootblog

protectedviewslayoutsmainphp as follows

ltdiv id=sidebargt

ltphp if(Yiiapp()-gtuser-gtisGuest) $this-gtwidget(rsquoUserMenursquo) gt

ltphp $this-gtwidget(rsquoTagCloudrsquo array(

rsquomaxTagsrsquo=gtYiiapp()-gtparams[rsquotagCloudCountrsquo]

)) gt

ltdivgt

54 Creating Recent Comments Portlet 45

54 Creating Recent Comments Portlet

In this section we create the last portlet that displays a list of comments recently pub-lished

541 Creating RecentComments Class

We create the RecentComments class in the file wwwrootblogprotectedcomponentsRecentCommentsphp The file has the following content

Yiiimport(rsquoziiwidgetsCPortletrsquo)

class RecentComments extends CPortlet

public $title=rsquoRecent Commentsrsquo

public $maxComments=10

public function getRecentComments()

return Commentmodel()-gtfindRecentComments($this-gtmaxComments)

protected function renderContent()

$this-gtrender(rsquorecentCommentsrsquo)

In the above we invoke the findRecentComments method which is defined in the Comment

class as follows

class Comment extends CActiveRecord

public function findRecentComments($limit=10)

return $this-gtwith(rsquopostrsquo)-gtfindAll(array(

rsquoconditionrsquo=gtrsquotstatus=rsquoselfSTATUS APPROVED

rsquoorderrsquo=gtrsquotcreate time DESCrsquo

rsquolimitrsquo=gt$limit

))

46 5 Portlets

542 Creating recentComments View

The recentComments view is saved in the file wwwrootblogprotectedcomponentsviews

recentCommentsphp It simply displays every comment returned by the RecentComments

getRecentComments() method

543 Using RecentComments Portlet

We modify the layout file wwwrootblogprotectedviewslayoutsmainphp to embedthis last portlet

ltdiv id=sidebargt

ltphp if(Yiiapp()-gtuser-gtisGuest) $this-gtwidget(rsquoUserMenursquo) gt

ltphp $this-gtwidget(rsquoTagCloudrsquo array(

rsquomaxTagsrsquo=gtYiiapp()-gtparams[rsquotagCloudCountrsquo]

)) gt

ltphp $this-gtwidget(rsquoRecentCommentsrsquo array(

rsquomaxCommentsrsquo=gtYiiapp()-gtparams[rsquorecentCommentCountrsquo]

)) gt

ltdivgt

Chapter 6

Final Work

61 Beautifying URLs

The URLs linking various pages of our blog application currently look ugly For examplethe URL for the page showing a post looks like the following

indexphpr=postshowampid=1

In this section we describe how to beautify these URLs and make them SEO-friendlyOur goal is to be able to use the following URLs in the application

1 indexphppostsyii leads to the page showing a list of posts with tag yii

2 indexphppost2A+Test+Post leads to the page showing the detail of the postwith ID 2 whose title is A Test Post

3 indexphppostupdate1 leads to the page that allows updating the post with ID1

Note that in the second URL format we include the post title in the URL This is mainlyto make the URL SEO friendly It is said that search engines may also respect the wordsfound in a URL when it is being indexed

To achieve our goal we modify the application configuration as follows

return array(

rsquocomponentsrsquo=gtarray(

rsquourlManagerrsquo=gtarray(

rsquourlFormatrsquo=gtrsquopathrsquo

rsquorulesrsquo=gtarray(

48 6 Final Work

rsquopostltidd+gtlttitlegtrsquo=gtrsquopostviewrsquorsquopostslttaggtrsquo=gtrsquopostindexrsquo

)

)

)

)

In the above we configure the urlManager component by setting its urlFormat propertyto be path and adding a set of rules

The rules are used by urlManager to parse and create the URLs in the desired formatFor example the first rule says that if a URL indexphppostsyii is requested theurlManager component should be responsible to dispatch the request to the route post

index and generate a tag GET parameter with the value yii On the other hand whencreating a URL with the route postindex and parameter tag the urlManager componentwill also use this rule to generate the desired URL indexphppostsyii For this reasonwe say that urlManager is a two-way URL manager

The urlManager component can further beautify our URLs such as hiding indexphp inthe URLs appending suffix like html to the URLs We can obtain these features easilyby configuring various properties of urlManager in the application configuration For moredetails please refer to the Guide

62 Logging Errors

A production Web application often needs sophisticated logging for various events In ourblog application we would like to log the errors occurring when it is being used Sucherrors could be programming mistakes or usersrsquo misuse of the system Logging these errorswill help us to improve the blog application

We enable the error logging by modifying the application configuration as follows

return array(

rsquopreloadrsquo=gtarray(rsquologrsquo)

rsquocomponentsrsquo=gtarray(

rsquologrsquo=gtarray(

rsquoclassrsquo=gtrsquoCLogRouterrsquo

rsquoroutesrsquo=gtarray(

array(

rsquoclassrsquo=gtrsquoCFileLogRoutersquo

rsquolevelsrsquo=gtrsquoerror warningrsquo

63 Final Tune-up and Deployment 49

)

)

)

)

)

With the above configuration if an error or warning occurs detailed information will belogged and saved in a file located under the directory wwwrootblogprotectedruntime

The log component offers more advanced features such as sending log messages to a listof email addresses displaying log messages in JavaScript console window etc For moredetails please refer to the Guide

63 Final Tune-up and Deployment

We are close to finish our blog application Before deployment we would like to do sometune-ups

631 Changing Home Page

We change to use the post list page as the home page We modify the application config-uration as follows

return array(

rsquodefaultControllerrsquo=gtrsquopostrsquo

)

Tip Because PostController already declares list to be its default action whenwe access the home page of the application we will see the result generated by thelist action of the post controller

632 Enabling Schema Caching

Because ActiveRecord relies on the metadata about tables to determine the column infor-mation it takes time to read the metadata and analyze it This may not be a problemduring development stage but for an application running in production mode it is a totalwaste of time if the database schema does not change Therefore we should enable theschema caching by modifying the application configuration as follows

50 6 Final Work

return array(

rsquocomponentsrsquo=gtarray(

rsquocachersquo=gtarray(

rsquoclassrsquo=gtrsquoCDbCachersquo

)

rsquodbrsquo=gtarray(

rsquoclassrsquo=gtrsquosystemdbCDbConnectionrsquo

rsquoconnectionStringrsquo=gtrsquosqlitewwwrootblogprotecteddatablogdbrsquo

rsquoschemaCachingDurationrsquo=gt3600

)

)

)

In the above we first add a cache component which uses a default SQLite database as thecaching storage If our server is equipped with other caching extensions such as APCwe could change to use them as well We also modify the db component by setting itsschemaCachingDuration property to be 3600 which means the parsed database schemadata can remain valid in cache for 3600 seconds

633 Disabling Debugging Mode

We modify the entry script file wwwrootblogindexphp by removing the line definingthe constant YII DEBUG This constant is useful during development stage because it allowsYii to display more debugging information when an error occurs However when theapplication is running in production mode displaying debugging information is not agood idea because it may contain sensitive information such as where the script file islocated and the content in the file etc

634 Deploying the Application

The final deployment process manly involves copying the directory wwwrootblog to thetarget directory The following checklist shows every needed step

1 Install Yii in the target place if it is not available

2 Copy the entire directory wwwrootblog to the target place

3 Edit the entry script file indexphp by pointing the $yii variable to the new Yiibootstrap file

4 Edit the file protectedyiicphp by setting the $yiic variable to be the new Yiiyiicphp file

64 Future Enhancements 51

5 Change the permission of the directories assets and protectedruntime so that theyare writable by the Web server process

64 Future Enhancements

641 Using a Theme

Without writing any code our blog application is already themeable To use a themewe mainly need to develop the theme by writing customized view files in the theme Forexample to use a theme named classic that uses a different page layout we would createa layout view file wwwrootblogthemesclassicviewslayoutsmainphp We also needto change the application configuration to indicate our choice of the classic theme

return array(

rsquothemersquo=gtrsquoclassicrsquo

)

642 Internationalization

We may also internationalize our blog application so that its pages can be displayed indifferent languages This mainly involves efforts in two aspects

First we may create view files in different languages For example for the list pageof PostController we can create a view file wwwrootblogprotectedviewspostzh cn

listphp When the application is configured to use simplified Chinese (the language codeis zh cn) Yii will automatically use this new view file instead of the original one

Second we may create message translations for those messages generated by code Themessage translations should be saved as files under the directory wwwrootblogprotected

messages We also need to modify the code where we use text strings by enclosing themin the method call Yiit()

For more details about internationalization please refer to the Guide

643 Improving Performance with Cache

While the Yii framework itself is very efficient it is not necessarily true that an applicationwritten in Yii efficient There are several places in our blog application that we can improvethe performance For example the tag clould portlet could be one of the performancebottlenecks because it involves complex database query and PHP logic

52 6 Final Work

We can make use of the sophisticated caching feature provided by Yii to improve theperformance One of the most useful components in Yii is COutputCache which caches afragment of page display so that the underlying code generating the fragment does not needto be executed for every request For example in the layout file wwwrootblogprotected

viewslayoutsmainphp we can enclose the tag cloud portlet with COutputCache

ltphp if($this-gtbeginCache(rsquotagCloudrsquo array(rsquodurationrsquo=gt3600))) gt

ltphp $this-gtwidget(rsquoTagCloudrsquo array(

rsquomaxTagsrsquo=gtYiiapp()-gtparams[rsquotagCloudCountrsquo]

)) gt

ltphp $this-gtendCache() gt

With the above code the tag cloud display will be served from cache instead of beinggenerated on-the-fly for every request The cached content will remain valid in cache for3600 seconds

644 Adding New Features

Our blog application only has very basic functionalities To become a complete blogsystem more features are needed for example calendar portlet email notifications postcategorization archived post portlet and so on We will leave the implementation of thesefeatures to interested readers

  • Contents
  • License
  • Getting Started
    • Building a Blog System using Yii
    • Testdriving with Yii
      • Installing Yii
      • Creating Skeleton Application
      • Application Workflow
        • Requirements Analysis
        • Overall Design
          • Initial Prototyping
            • Setting Up Database
              • Creating Database
              • Establishing Database Connection
                • Scaffolding
                • Authenticating User
                • Summary
                  • Post Management
                    • Customizing Post Model
                      • Customizing rules() Method
                      • Customizing relations() Method
                      • Representing Status in Text
                        • Creating and Updating Posts
                          • Customizing Access Control
                          • Customizing create and update Operations
                            • Displaying Posts
                              • Customizing view Operation
                              • Customizing index Operation
                                • Managing Posts
                                  • Listing Posts in Tabular View
                                  • Deleting Posts
                                      • Comment Management
                                        • Customizing Comment Model
                                          • Customizing rules() Method
                                          • Customizing attributeLabels() Method
                                          • Customizing Saving Process
                                            • Creating and Displaying Comments
                                              • Displaying Comments
                                              • Creating Comments
                                                • Managing Comments
                                                  • Updating and Deleting Comments
                                                  • Approving Comments
                                                      • Portlets
                                                        • Customizing Page Layout
                                                        • Creating User Menu Portlet
                                                          • Creating UserMenu Class
                                                          • Creating userMenu View
                                                          • Using UserMenu Portlet
                                                          • Testing UserMenu Portlet
                                                          • Summary
                                                            • Creating Tag Cloud Portlet
                                                              • Creating TagCloud Class
                                                              • Using TagCloud Portlet
                                                                • Creating Recent Comments Portlet
                                                                  • Creating RecentComments Class
                                                                  • Creating recentComments View
                                                                  • Using RecentComments Portlet
                                                                      • Final Work
                                                                        • Beautifying URLs
                                                                        • Logging Errors
                                                                        • Final Tune-up and Deployment
                                                                          • Changing Home Page
                                                                          • Enabling Schema Caching
                                                                          • Disabling Debugging Mode
                                                                          • Deploying the Application
                                                                            • Future Enhancements
                                                                              • Using a Theme
                                                                              • Internationalization
                                                                              • Improving Performance with Cache
                                                                              • Adding New Features
Page 16: Yii Blog 1.1.0 Demo Tutor

Chapter 2

Initial Prototyping

21 Setting Up Database

Having created a skeleton application and finished the database design in this section wewill create the blog database and establish the connection to it in the skeleton application

211 Creating Database

We choose to create a SQLite database Because the database support in Yii is built on topof PDO we can easily switch to use a different type of DBMS (eg MySQL PostgreSQL)without the need to change our application code

We create the database file blogdb under the directory wwwrootblogprotecteddataNote that both the directory and the database file have to be writable by the Web serverprocess as required by SQLite We may simply copy the database file from the blogdemo in our Yii installation which is located at wwwrootyiidemosblogprotecteddatablogdb We may also generate the database by executing the SQL statements in the filewwwrootyiidemosblogprotecteddataschemasqlitesql

Tip To execute SQL statements we may use the sqlite3 command line tool thatcan be found in the SQLite official website

212 Establishing Database Connection

To use the blog database in the skeleton application we created we need to modify itsapplication configuration which is stored in the PHP script wwwrootblogprotected

configmainphp The script returns an associative array consisting of name-value pairseach of which is used to initialize a writable property of the application instance

We configure the db component as follows

10 2 Initial Prototyping

return array(

rsquocomponentsrsquo=gtarray(

rsquodbrsquo=gtarray(

rsquoconnectionStringrsquo=gtrsquosqlitewwwrootblogprotecteddatablogdbrsquo

rsquotablePrefixrsquo=gtrsquotbl rsquo

)

)

)

The above configuration says that we have a db application component whose connectionStringproperty should be initialized as sqlitewwwrootblogprotecteddatablogdb and whosetablePrefix property should be tbl

With this configuration we can access the DB connection object using Yiiapp()-gtdb

at any place in our code Note that Yiiapp() returns the application instance that wecreate in the entry script If you are interested in possible methods and properties that theDB connection has you may refer to its class reference However in most cases we are notgoing to use this DB connection directly Instead we will use the so-called ActiveRecordto access the database

We would like to explain a bit more about the tablePrefix property that we set in theconfiguration This tells the db connection that it should respect the fact we are usingtbl as the prefix to our database table names In particular if in a SQL statement thereis a token enclosed within double curly brackets (eg post then the db connectionshould translate it into a name with the table prefix (eg tbl post) before sending it toDBMS for execution This feature is especially useful if in future we need to modify thetable name prefix without touching our source code For example if we are developing ageneric content management system (CMS) we may exploit this feature so that when itis being installed in a new environment we can allow users to choose a table prefix theylike

22 Scaffolding 11

Tip If you want to use MySQL instead of SQLite to store data you may cre-ate a MySQL database named blog using the SQL statements in wwwrootyii

demosblogprotecteddataschemamysqlsql Then modify the applicationconfiguration as follows

return array(

rsquocomponentsrsquo=gtarray(

rsquodbrsquo=gtarray(

rsquoconnectionStringrsquo =gt rsquomysqlhost=localhostdbname=blogrsquo

rsquoemulatePreparersquo =gt true

rsquousernamersquo =gt rsquorootrsquo

rsquopasswordrsquo =gt rsquorsquo

rsquocharsetrsquo =gt rsquoutf8rsquo

rsquotablePrefixrsquo =gt rsquotbl rsquo

)

)

)

22 Scaffolding

Create read update and delete (CRUD) are the four basic operations of data objects in anapplication Because the task of implementing the CRUD operations is so common whendeveloping Web applications Yii provides a tool to automate this process (also known asscaffolding) In this section we will describe how to use the tool to implement CRUD forposts and comments

Open a command window and run the following commands

wwwrootyiiframeworkyiic shell wwwrootblogindexphp

Yii Interactive Tool v11

Please type rsquohelprsquo for help Type rsquoexitrsquo to quit

gtgt model

gtgt crud Post

gtgt crud Comment

gtgt exit

12 2 Initial Prototyping

Info Some PHP installations may use a different phpini file for command line(CLI) PHP parser As a result when running the above yiic commands youmay encounter errors like rdquoYiiBaseinclude(PDOphp) failed to open streamrdquoor rdquocould not find driverrdquo Please double check your CLI PHP configuration byexecuting the following command

php -r phpinfo()

The result of the above command will show which phpini file is being used andwhich extensions are loaded If a wrong phpini file is used you may use thefollowing command to explicitly specify the correct phpini to use

php -c phpini wwwrootyiiframeworkyiicphp shell wwwrootblogindexphp

The commands above accomplish two tasks First the model command generates a modelclass file for each table in the blog database Second the crud commands generate thecode needed by the CRUD operations for the Post and Comment models

Tip The command model generates a model class for every table in the databaseSometimes we probably donrsquot want to do so (eg the database contains some irrel-evant tables) In this case we can create model classes one by one For exampleto create the User model we can use the command model User The model com-mand also has some more advanced usages For more details execute help model

command

We can test the generated code by accessing the following URLs

httpwwwexamplecomblogindexphpr=post

httpwwwexamplecomblogindexphpr=comment

Notice that the post and comment features implemented by the generated code are com-pletely independent of each other Also when creating a new post or comment we arerequired to enter information such as author id and create time which in real applica-tion should be set by the program Donrsquot worry We will fix these problems in the nextmilestones For now we should be fairly satisfied as this prototype already contains mostfeatures that we need to implement for the blog application

To prepare for the next milestones letrsquos take a closer look at the files generated by theabove commands All the files are generated under wwwrootblogprotected For conve-nience we group them into model files controller files and view files

22 Scaffolding 13

bull model files

ndash modelsUserphp contains the User class that extends from CActiveRecord andcan be used to access the tbl user database table

ndash modelsPostphp contains the Post class that extends from CActiveRecord andcan be used to access the tbl post database table

ndash modelsTagphp contains the Tag class that extends from CActiveRecord andcan be used to access the tbl tag database table

ndash modelsCommentphp contains the Comment class that extends from CActiveRe-cord and can be used to access the tbl comment database table

ndash modelsLookupphp contains the Lookup class that extends from CActiveRecordand can be used to access the tbl lookup database table

bull controller file

ndash controllersPostControllerphp contains the PostController class which is thecontroller in charge of all CRUD operations about posts

ndash controllersCommentControllerphp contains the CommentController class whichis the controller in charge of all CRUD operations about comments

bull view files

ndash viewspostcreatephp is the view file that shows an HTML form to create anew post

ndash viewspostupdatephp is the view file that shows an HTML form to update anexisting post

ndash viewspostviewphp is the view file that displays the detailed information of apost

ndash viewspostindexphp is the view file that displays a list of posts

ndash viewspostadminphp is the view file that displays posts in a table with ad-ministrative commands

ndash viewspost formphp is the partial view file embedded in viewspostcreate

php and viewspostupdatephp It displays the HTML form for collecting postinformation

ndash viewspost viewphp is the partial view file used by viewspostindexphp Itdisplays the brief view of a single post

ndash a similar set of view files are also generated for comment

14 2 Initial Prototyping

In order to understand better how the above files are used we show in the following theworkflow that occurs in the blog application when displaying a list of posts

1 The user requests the URL httpwwwexamplecomblogindexphpr=posts

2 The entry script is executed by the Web server which creates and initializes anapplication instance to handle the request

3 The application creates an instance of PostController and executes it

4 The PostController instance executes the index action by calling its actionIndex()

method Note that index is the default action if the user does not specify an actionto execute in the URL

5 The actionIndex() method queries database to bring back the list of recent posts

6 The actionIndex() method renders the index view with the post data

23 Authenticating User

Our blog application needs to differentiate between the system owner and guest usersTherefore we need to implement the user authentication feature

As you may have found that the skeleton application already provides user authenticationby checking if the username and password are both demo or admin In this section wewill modify the corresponding code so that the authentication is done against the User

database table

User authentication is performed in a class implementing the IUserIdentity interface Theskeleton application uses the UserIdentity class for this purpose The class is stored inthe file wwwrootblogprotectedcomponentsUserIdentityphp

Tip By convention the name of a class file must be the same as the correspondingclass name suffixed with the extension php Following this convention one canrefer to a class using a path alias For example we can refer to the UserIdentity

class with the alias applicationcomponentsUserIdentity Many APIs in Yiican recognize path aliases (eg YiicreateComponent()) and using path aliasesavoids the necessity of embedding absolute file paths in the code The existence ofthe latter often causes trouble when we deploy an application

We modify the UserIdentity class as follows

23 Authenticating User 15

ltphp

class UserIdentity extends CUserIdentity

private $ id

public function authenticate()

$username=strtolower($this-gtusername)

$user=Usermodel()-gtfind(rsquoLOWER(username)=rsquoarray($username))

if($user===null)

$this-gterrorCode=selfERROR USERNAME INVALID

else if($user-gtvalidatePassword($this-gtpassword))

$this-gterrorCode=selfERROR PASSWORD INVALID

else

$this-gt id=$user-gtid

$this-gtusername=$user-gtusername

$this-gterrorCode=selfERROR NONE

return $this-gterrorCode==selfERROR NONE

public function getId()

return $this-gt id

In the authenticate() method we use the User class to look for a row in the User tablewhose username column is the same as the given username in a case-insensitive mannerRemember that the User class was created using the yiic tool in the prior section Becausethe User class extends from CActiveRecord we can exploit the ActiveRecord feature toaccess the User table in an OOP fashion

In order to check if the user has entered a valid password we invoke the validatePassword

method of the User class We need to modify the file wwwrootblogprotectedmodels

Userphp as follows Note that instead of storing the plain password in the database westore the hash result of the password and a randomly generated salt key When validatingthe user-entered password we should compare the hash results instead

class User extends CActiveRecord

public function validatePassword($password)

return $this-gthashPassword($password$this-gtsalt)===$this-gtpassword

16 2 Initial Prototyping

public function hashPassword($password$salt)

return md5($salt$password)

In the UserIdentity class we also override the getId() method which returns the id valueof the user found in the User table The parent implementation would return the usernameinstead Both the username and id properties will be stored in the user session and maybe accessed via Yiiapp()-gtuser from anywhere in our code

Tip In the UserIdentity class we reference the class CUserIdentity without ex-plicitly including the corresponding class file This is because CUserIdentity is acore class provided by the Yii framework Yii will automatically include the classfile for any core class when it is referenced for the first time

We also do the same with the User class This is because the User class file is placedunder the directory wwwrootblogprotectedmodels which has been added tothe PHP include path according to the following lines found in the applicationconfiguration

return array(

rsquoimportrsquo=gtarray(

rsquoapplicationmodelsrsquo

rsquoapplicationcomponentsrsquo

)

)

The above configuration says that any class whose class file is located under either wwwrootblogprotectedmodels or wwwrootblogprotectedcomponents willbe automatically included when the class is referenced for the first time

The UserIdentity class is mainly used by the LoginForm class to authenticate a user basedon the username and password input collected from the login page The following codefragment shows how UserIdentity is used

$identity=new UserIdentity($username$password)

$identity-gtauthenticate()

switch($identity-gterrorCode)

24 Summary 17

case UserIdentityERROR NONE

Yiiapp()-gtuser-gtlogin($identity)

break

Info People often get confused about identity and the user application componentThe former represents a way of performing authentication while the latter is usedto represent the information related with the current user An application can onlyhave one user component but it can have one or several identity classes dependingon what kind of authentication it supports Once authenticated an identity instancemay pass its state information to the user component so that they are globallyaccessible via user

To test the modified UserIdentity class we can browse the URL httpwwwexample

comblogindexphp and try logging in with the username and password that we store inthe User table If we use the database provided by the blog demo we should be able tologin with username demo and password demo Note that this blog system does not providethe user management feature As a result a user cannot change his account or create anew one through the Web interface The user management feature may be considered asa future enhancement to the blog application

24 Summary

We have completed the milestone 1 Letrsquos summarize what we have done so far

1 We identified the requirements to be fulfilled

2 We installed the Yii framework

3 We created a skeleton application

4 We designed and created the blog database

5 We modified the application configuration by adding the database connection

6 We generated the code that implements the basic CRUD operations for both postsand comments

7 We modified the authentication method to check against the tbl user table

18 2 Initial Prototyping

For a new project most of the time will be spent in step 1 and 4 for this first milestone

Although the code generated by the yiic tool implements fully functional CRUD oper-ations for a database table it often needs to be modified in practical applications Forthis reason in the next two milestone our job is to customize the generated CRUD codeabout posts and comments so that it reaches our initial requirements

In general we first modify the model class file by adding appropriate validation rules anddeclaring relational objects We then modify the controller action and view code for eachindividual CRUD operation

Chapter 3

Post Management

31 Customizing Post Model

The Post model class generated by the yiic tool mainly needs to be modified in two places

bull the rules() method specifies the validation rules for the model attributes

bull the relations() method specifies the related objects

Info A model consists of a list of attributes each associated with a column in thecorresponding database table Attributes can be declared explicitly as class membervariables or implicitly without any declaration

311 Customizing rules() Method

We first specify the validation rules which ensure the attribute values entered by usersare correct before they are saved to the database For example the status attribute ofPost should be an integer 1 2 or 3 The yiic tool also generates validation rules for eachmodel However these rules are based on the table column information and may not beappropriate

Based on the requirement analysis we modify the rules() method as follows

public function rules()

return array(

array(rsquotitle content statusrsquo rsquorequiredrsquo)

array(rsquotitlersquo rsquolengthrsquo rsquomaxrsquo=gt128)

array(rsquostatusrsquo rsquoinrsquo rsquorangersquo=gtarray(123))

array(rsquotagsrsquo rsquomatchrsquo rsquopatternrsquo=gtrsquo^[ws]+$rsquorsquomessagersquo=gtrsquoTags can only contain word charactersrsquo)

20 3 Post Management

array(rsquotagsrsquo rsquonormalizeTagsrsquo)

)

In the above we specify that the title content and status attributes are required thelength of title should not exceed 128 the status attribute value should be 1 (draft) 2(published) or 3 (archived) and the tags attribute should only contain word charactersand commas In addition we use normalizeTags to normalize the user-entered tags so thatthe tags are unique and properly separated with commas

The validators such as required length in and match are all built-in validators providedby Yii The normalizeTags validator is a method-based validator that we need to definein the Post class For more information about how to specify validation rules please referto the Guide

public function normalizeTags($attribute$params)

$this-gttags=Tagarray2string(array unique(Tagstring2array($this-gttags)))

The rules declared in this method are executed one by one when we call the validate() orsave() method of the model instance

Note It is very important to remember that attributes appearing in rules() mustbe those to be entered by end users Other attributes such as id and create time

in the Post model which are set by our code or database should not be in rules()For more details please refer to Securing Attribute Assignments

After making these changes we can visit the post creation page again to verify that thenew validation rules are taking effect

312 Customizing relations() Method

Lastly we customize the relations() method to specify the related objects of a post Bydeclaring these related objects in relations() we can exploit the powerful RelationalActiveRecord (RAR) feature to access the related object information of a post such asits author and comments without the need to write complex SQL JOIN statements

We customize the relations() method as follows

31 Customizing Post Model 21

public function relations()

return array(

rsquoauthorrsquo =gt array(selfBELONGS TO rsquoUserrsquo rsquoauthor idrsquo)

rsquocommentsrsquo =gt array(selfHAS MANY rsquoCommentrsquo rsquopost idrsquo

rsquoconditionrsquo=gtrsquocommentsstatus=rsquoCommentSTATUS APPROVED

rsquoorderrsquo=gtrsquocommentscreate time DESCrsquo)

rsquocommentCountrsquo =gt array(selfSTAT rsquoCommentrsquo rsquopost idrsquo

rsquoconditionrsquo=gtrsquostatus=rsquoCommentSTATUS APPROVED)

)

The above relations state that

bull A post belongs to an author whose class is User and the relationship is establishedbased on the author id attribute value of the post

bull A post has many comments whose class is Comment and the relationship is establishedbased on the post id attribute value of the comments These comments should besorted according to their creation time and the comments must be approved

bull The commentCount relation is a bit special as it returns back an aggregation resultwhich is about how many comments the post has

With the above relation declaration we can easily access the author and comments of apost like the following

$author=$post-gtauthor

echo $author-gtusername

$comments=$post-gtcomments

foreach($comments as $comment)

echo $comment-gtcontent

For more details about how to declare and use relations please refer to the Guide

313 Representing Status in Text

Because the status of a post is stored as an integer in the database we need to provide atextual representation so that it is more intuitive when being displayed to end users In alarge system the similar requirement is very common

22 3 Post Management

As a generic solution we use the tbl lookup table to store the mapping between integervalues and textual representations that are needed by other data objects We modify theLookup model class as follows to more easily access the textual data in the table

class Lookup extends CActiveRecord

private static $ items=array()

public static function items($type)

if(isset(self$ items[$type]))

selfloadItems($type)

return self$ items[$type]

public static function item($type$code)

if(isset(self$ items[$type]))

selfloadItems($type)

return isset(self$ items[$type][$code]) self$ items[$type][$code] false

private static function loadItems($type)

self$ items[$type]=array()

$models=selfmodel()-gtfindAll(array(

rsquoconditionrsquo=gtrsquotype=typersquo

rsquoparamsrsquo=gtarray(rsquotypersquo=gt$type)

rsquoorderrsquo=gtrsquopositionrsquo

))

foreach($models as $model)

self$ items[$type][$model-gtcode]=$model-gtname

Our new code mainly provides two static methods Lookupitems() and Lookupitem()The former returns a list of strings belonging to the specified data type while the latterreturns a particular string for the given data type and data value

Our blog database is pre-populated with two lookup types PostStatus and CommentStatusThe former refers to the possible post statuses while the latter the comment statuses

In order to make our code easier to read we also declare a set of constants to representthe status integer values We should use these constants through our code when referringto the corresponding status values

32 Creating and Updating Posts 23

class Post extends CActiveRecord

const STATUS DRAFT=0

const STATUS PUBLISHED=1

const STATUS ARCHIVED=2

Therefore we can call Lookupitems(rsquoPostStatusrsquo) to get the list of possible post statuses(text strings indexed by the corresponding integer values) and call Lookupitem(rsquoPostStatusrsquoPostSTATUS PUBLISHED) to get the string representation of the published status

32 Creating and Updating Posts

With the Post model ready we need to fine-tune the actions and views for the controllerPostController In this section we first customize the access control of CRUD operationswe then modify the code implementing the create and update operations

321 Customizing Access Control

The first thing we want to do is to customize the access control because the code generatedby yiic does not fit our needs

We modify the accessRules() method in the file wwwrootblogprotectedcontrollers

PostControllerphp as follows

public function accessRules()

return array(

array(rsquoallowrsquo allow all users to perform rsquolistrsquo and rsquoshowrsquo actions

rsquoactionsrsquo=gtarray(rsquoindexrsquo rsquoviewrsquo)

rsquousersrsquo=gtarray(rsquorsquo)

)

array(rsquoallowrsquo allow authenticated users to perform any action

rsquousersrsquo=gtarray(rsquorsquo)

)

array(rsquodenyrsquo deny all users

rsquousersrsquo=gtarray(rsquorsquo)

)

)

The above rules state that all users can access the index and view actions and authenti-cated users can access any actions including the admin action The user should be denied

24 3 Post Management

access in any other scenario Note that these rules are evaluated in the order they arelisted here The first rule matching the current context makes the access decision Forexample if the current user is the system owner who tries to visit the post creation pagethe second rule will match and it will give the access to the user

322 Customizing create and update Operations

The create and update operations are very similar They both need to display an HTMLform to collect user inputs validate them and save them into database The main differ-ence is that the update operation will pre-populate the form with the existing post datafound in the database For this reason the yiic tool generates a partial view wwwroot

blogprotectedviewspost formphp that is embedded in both the create and update

views to render the needed HTML form

We first change the formphp file so that the HTML form only collects the inputs wewant title content tags and status We use plain text fields to collect inputs for thefirst three attributes and a dropdown list to collect input for status The dropdown listoptions are the text displays of the possible post statuses

ltphp echo CHtmlactiveDropDownList($postrsquostatusrsquoLookupitems(rsquoPostStatusrsquo)) gt

In the above we call Lookupitems(rsquoPostStatusrsquo) to bring back the list of post statuses

We then modify the Post class so that it can automatically set some attributes (eg create

time author id) before a post is saved to the database We override the beforeSave()

method as follows

protected function beforeSave()

if(parentbeforeSave())

if($this-gtisNewRecord)

$this-gtcreate time=$this-gtupdate time=time()

$this-gtauthor id=Yiiapp()-gtuser-gtid

else

$this-gtupdate time=time()

return true

else

return false

33 Displaying Posts 25

When we save a post we want to update the tbl tag table to reflect the change of tagfrequencies We can do this work in the afterSave() method which is automaticallyinvoked by Yii after a post is successfully saved into the database

protected function afterSave()

parentafterSave()

Tagmodel()-gtupdateFrequency($this-gt oldTags $this-gttags)

private $ oldTags

protected function afterFind()

parentafterFind()

$this-gt oldTags=$this-gttags

In the implementation because we want to detect if the user changes the tags in case he isupdating an existing post we need to know what the old tags are For this reason we alsowrite the afterFind() method to keep the old tags in the variable oldTags The methodafterFind() is invoked automatically by Yii when an AR record is populated with thedata from database

We are not going to give details of the TagupdateFrequency() method here Interestedreaders may refer to the file wwwrootyiidemosblogprotectedmodelsTagphp

33 Displaying Posts

In our blog application a post may be displayed among a list of posts or by itself Theformer is implemented as the index operation while the latter the view operation In thissection we customize both operations to fulfill our initial requirements

331 Customizing view Operation

The view operation is implemented by the actionView() method in PostController Itsdisplay is generated by the view view with the view file wwwrootblogprotectedviews

postviewphp

Below is the relevant code implementing the view operation in PostController

public function actionView()

26 3 Post Management

$this-gtrender(rsquoviewrsquoarray(

$post=$this-gtloadModel()

$this-gtrender(rsquoviewrsquoarray(

rsquomodelrsquo=gt$post

))

))

private $ model

public function loadModel()

if($this-gt model===null)

if(isset($ GET[rsquoidrsquo]))

if(Yiiapp()-gtuser-gtisGuest)

$condition=rsquostatus=rsquoPostSTATUS PUBLISHED

rsquo OR status=rsquoPOSTSTATUS ARCHIVED

else

$condition=rsquorsquo

$this-gt model=Postmodel()-gtfindbyPk($ GET[rsquoidrsquo] $condition)

if($this-gt model===null)

throw new CHttpException(404rsquoThe requested page does not existrsquo)

return $this-gt model

Our change mainly lies in the loadModel() method In this method we query the Post

table according to the id GET parameter If the post is not found or if it is not publishedor archived (when the user is a guest) we will throw a 404 HTTP error Otherwise thepost object is returned to actionView() which in turn passes the post object to the viewscript for further display

Tip Yii captures HTTP exceptions (instances of CHttpException) and displaysthem in either predefined templates or customized error views The skeleton appli-cation generated by yiic already contains a customized error view in wwwroot

blogprotectedviewssiteerrorphp We can modify this file if we want tofurther customize the error display

The change in the view script is mainly about ajdusting the formatting and styles of thepost display We will not go into details here Interested readers may refer to wwwroot

blogprotectedviewspostviewphp

33 Displaying Posts 27

332 Customizing index Operation

Like the view operation we customize the index operation in two places the actionIndex()

method in PostController and the view file wwwrootblogprotectedviewspostindex

php We mainly need to add the support for displaying a list of posts that are associatedwith a specified tag

Below is the modified actionIndex() method in PostController

public function actionIndex()

$criteria=new CDbCriteria(array(

rsquoconditionrsquo=gtrsquostatus=rsquoPostSTATUS PUBLISHED

rsquoorderrsquo=gtrsquoupdate time DESCrsquo

rsquowithrsquo=gtrsquocommentCountrsquo

))

if(isset($ GET[rsquotagrsquo]))

$criteria-gtaddSearchCondition(rsquotagsrsquo$ GET[rsquotagrsquo])

$dataProvider=new CActiveDataProvider(rsquoPostrsquo array(

rsquopaginationrsquo=gtarray(

rsquopageSizersquo=gtYiiapp()-gtparams[rsquopostsPerPagersquo]

)

rsquocriteriarsquo=gt$criteria

))

$this-gtrender(rsquoindexrsquoarray(

rsquodataProviderrsquo=gt$dataProvider

))

In the above we first create a query criteria for retrieving post list The criteria statesthat only published posts should be returned and they should be sorted according to theirupdate time in descending order Because when displaying a post in the list we want toshow how many comments the post has received in the criteria we also specify to bringback commentCount which if you remember is a relation declared in Postrelations()

In case when a user wants to see posts with a specific tag we would add a search conditionto the criteria to look for the specified tag

Using the query criteria we create a data provider which mainly serves for three purposesFirst it does pagination of the data when too many results may be returned Herewe customize the pagination by setting the page size to be an application parameterpostsPerPage Second it does sorting according to the user request And finally it feedsthe paginated and sorted data to widgets or view code for presentation

28 3 Post Management

After we finish with actionIndex() we modify the index view as follows Our change ismainly about adding the h1 header when the user specifies to display posts with a tag

ltphp if(empty($ GET[rsquotagrsquo])) gt

lth1gtPosts Tagged with ltigtltphp echo CHtmlencode($ GET[rsquotagrsquo]) gtltigtlth1gt

ltphp endif gt

ltphp $this-gtwidget(rsquoziiwidgetsCListViewrsquo array(

rsquodataProviderrsquo=gt$dataProvider

rsquoitemViewrsquo=gtrsquo viewrsquo

rsquotemplatersquo=gtitemsnpager)) gt

Note that in the above we use [CListView] to display the post list This widget requires apartial view to display the detail of each individual post Here we specify the partial viewto be view which means the file wwwrootblogprotectedviewspost viewphp In thisview script we can acccess the post instance being displayed via a local variable named$data

34 Managing Posts

Managing posts mainly refers to listing posts in an administrative view that allows us tosee posts with all statuses updating them and deleting them They are accomplished bythe admin operation and the delete operation respectively The code generated by yiic

does not need much modification Below we mainly explain how these two operations areimplemented

341 Listing Posts in Tabular View

The admin operation shows posts with all statuses in a tabular view The view supportssorting and pagination The following is the actionAdmin() method in PostController

public function actionAdmin()

$dataProvider=new CActiveDataProvider(rsquoPostrsquo array(

rsquosortrsquo=gtarray(

rsquodefaultOrderrsquo=gtrsquostatus update time DESCrsquo

)

))

$this-gtrender(rsquoadminrsquoarray(

rsquodataProviderrsquo=gt$dataProvider

))

34 Managing Posts 29

The above code is very similar to that in actionIndex() The main difference is in thequery criteria Here we do not specify any particular query criteria since we want to seeall posts We do customize the sort property of the data provider so that by default theposts are shown in the order of their statuses and update time

Below is the code for the admin view

lth1gtManage Postslth1gt

ltphp $this-gtwidget(rsquoziiwidgetsgridCGridViewrsquo array(

rsquodataProviderrsquo=gt$dataProvider

rsquocolumnsrsquo=gtarray(

array(

rsquonamersquo=gtrsquotitlersquo

rsquotypersquo=gtrsquorawrsquo

rsquovaluersquo=gtrsquoCHtmllink(CHtmlencode($data-gttitle) $data-gturl)rsquo

)

array(

rsquonamersquo=gtrsquostatusrsquo

rsquovaluersquo=gtrsquoLookupitem(PostStatus$data-gtstatus)rsquo

)

rsquocreate timedatetimersquo

rsquoupdate timedatetimersquo

array(

rsquoclassrsquo=gtrsquoCButtonColumnrsquo

)

)

)) gt

We use [CGridView] to display the posts It allows us to sort by a column and paginatethrough the posts if there are too many to be displayed in a single page Our change ismainly about how to display each column For example for the title column we specifythat it should be displayed as a hyperlink that points to the detailed view of the post

Tip When displaying text we call CHtmlencode() to encode HTML entities init This prevents from cross-site scripting attack

342 Deleting Posts

In the admin data grid there is a delete button in each row Clicking on the button shoulddelete the corresponding post Internally this triggers the delete action implemented asfollows

30 3 Post Management

public function actionDelete()

if(Yiiapp()-gtrequest-gtisPostRequest)

we only allow deletion via POST request

$this-gtloadModel()-gtdelete()

if(isset($ POST[rsquoajaxrsquo]))

$this-gtredirect(array(rsquoindexrsquo))

else

throw new CHttpException(400rsquoInvalid request Please do not repeat this request againrsquo)

The above code is the one generated by the yiic tool without any change We wouldlike to explain a little bit more about the checking on $ POST[rsquoajaxrsquo] The [CGridView]widget has a very nice feature that its sorting pagination and deletion operations are alldone in AJAX mode by default That means the whole page does not get reloaded if anyof the above operations is performed However it is also possible that the widget runs innon-AJAX mode (by setting its ajaxUpdate property to be false or disabling JavaScript onthe client side) It is necessary for the delete action to differentiate these two scenarios ifthe delete request is made via AJAX we should not redirect the user browser otherwisewe should

Deleting a post should also cause the deletion of all comments for that post In additionwe should also update the tbl tag table regarding the tags for the deleted post Both ofthese tasks can be achieved by writing an afterDelete method in the Post model class asfollows

protected function afterDelete()

parentafterDelete()

Commentmodel()-gtdeleteAll(rsquopost id=rsquo$this-gtid)

Tagmodel()-gtupdateFrequency($this-gttags rsquorsquo)

The above code is very straightforward it first deletes all those comments whose post id

is the same as the ID of the deleted post it then updates the tbl tag table for the tags

of the deleted post

34 Managing Posts 31

Tip We have to explicitly delete all comments for the deleted post here becauseSQLite does not really support foreign key constraints In a DBMS that supportsthis constraint (such as MySQL PostgreSQL) the foreign key constraint can be setup such that the DBMS automatically deletes the related comments if the post isdeleted In that case we no longer this explicit deletion call in our code

32 3 Post Management

Chapter 4

Comment Management

41 Customizing Comment Model

For the Comment model we mainly need to customize the rules() and attributeLabels()

methods The attributeLabels() method returns a mapping between attribute namesand attribute labels We do not need to touch relations() since the code generated bythe yiic tool is good enough

411 Customizing rules() Method

We first customize the validation rules generated by the yiic tool The following rules areused for comments

public function rules()

return array(

array(rsquocontent author emailrsquo rsquorequiredrsquo)

array(rsquoauthor email urlrsquo rsquolengthrsquo rsquomaxrsquo=gt128)

array(rsquoemailrsquorsquoemailrsquo)

array(rsquourlrsquorsquourlrsquo)

)

In the above we specify that the author email and content attributes are required thelength of author email and url cannot exceed 128 the email attribute must be a validemail address and the url attribute must be a valid URL

412 Customizing attributeLabels() Method

We then customize the attributeLabels() method to declare the label display for eachmodel attribute This method returns an array consisting of name-label pairs When wecall CHtmlactiveLabel() to display an attribute label

34 4 Comment Management

public function attributeLabels()

return array(

rsquoidrsquo =gt rsquoIdrsquo

rsquocontentrsquo =gt rsquoCommentrsquo

rsquostatusrsquo =gt rsquoStatusrsquo

rsquocreate timersquo =gt rsquoCreate Timersquo

rsquoauthorrsquo =gt rsquoNamersquo

rsquoemailrsquo =gt rsquoEmailrsquo

rsquourlrsquo =gt rsquoWebsitersquo

rsquopost idrsquo =gt rsquoPostrsquo

)

Tip If the label for an attribute is not declared in attributeLabels() an algo-rithm will be used to generate an appropriate label For example a label VerifyCode will be generated for attributes verify code or verifyCode

413 Customizing Saving Process

Because we want to record the creation time of a comment we override the beforeSave()

method of Comment like we do for the Post model

protected function beforeSave()

if(parentbeforeSave())

if($this-gtisNewRecord)

$this-gtcreate time=time()

return true

else

return false

42 Creating and Displaying Comments

In this section we implement the comment display and creation features

421 Displaying Comments

Instead of displaying and creating comments on individual pages we use the post detailpage (generated by the view action of PostController) Below the post content display

42 Creating and Displaying Comments 35

we display first a list of comments belonging to that post and then a comment creationform

In order to display comments on the post detail page we modify the view script wwwrootblogprotectedviewspostviewphp as follows

ltdiv id=commentsgt

ltphp if($model-gtcommentCountgt=1) gt

lth3gt

ltphp echo $model-gtcommentCount rsquocomment(s)rsquo gt

lth3gt

ltphp $this-gtrenderPartial(rsquo commentsrsquoarray(

rsquopostrsquo=gt$model

rsquocommentsrsquo=gt$model-gtcomments

)) gt

ltphp endif gt

ltdivgt

In the above we call renderPartial() to render a partial view named comments to displaythe list of comments belonging to the current post Note that in the view we use theexpression $model-gtcomments to retrieve the comments for the post This is valid becausewe have declared a comments relation in the Post class Evaluating this expression wouldtrigger an implicit JOIN database query to bring back the proper comments This featureis known as lazy relational query

The partial view comments is not very interesting It mainly goes through every commentand displays the detail of it Interested readers may refer to wwwrootyiidemosblog

protectedpost commentsphp

422 Creating Comments

To handle comment creation we first modify the actionView() method of PostController

as follows

public function actionView()

$post=$this-gtloadModel()

$comment=$this-gtnewComment($post)

$this-gtrender(rsquoviewrsquoarray(

rsquomodelrsquo=gt$post

rsquocommentrsquo=gt$comment

))

36 4 Comment Management

protected function newComment($post)

$comment=new Comment

if(isset($ POST[rsquoCommentrsquo]))

$comment-gtattributes=$ POST[rsquoCommentrsquo]

if($post-gtaddComment($comment))

if($comment-gtstatus==CommentSTATUS PENDING)

Yiiapp()-gtuser-gtsetFlash(rsquocommentSubmittedrsquorsquoThank yoursquo)

$this-gtrefresh()

return $comment

In the above we call the newComment() method before we render view In the newComment()

method we generate a Comment instance and check if the comment form is submitted If sowe try to add the comment for the post by calling $post-gtaddComment($comment) If it goesthrough we refresh the post detail page In case the comment needs to be approved we willshow a flash message to indicate this decision A flash message is usually a confirmationmessage displayed to end users If the user clicks on the refresh button of his browser themessage will disappear

We also need to modify wwwrootblogprotectedviewspostviewphp furthermore

ltdiv id=commentsgt

lth3gtLeave a Commentlth3gt

ltphp if(Yiiapp()-gtuser-gthasFlash(rsquocommentSubmittedrsquo)) gt

ltdiv class=successgt

ltphp echo Yiiapp()-gtuser-gtgetFlash(rsquocommentSubmittedrsquo) gt

ltdivgt

ltphp else gt

ltphp $this-gtrenderPartial(rsquocomment formrsquoarray(

rsquomodelrsquo=gt$comment

)) gt

ltphp endif gt

ltdivgtlt-- comments --gt

43 Managing Comments 37

In the above code we display the flash message if it is available If not we displaythe comment input form by rendering the partial view wwwrootblogprotectedviews

comment formphp

43 Managing Comments

Comment management includes updating deleting and approving comments These op-erations are implemented as actions in the CommentController class

431 Updating and Deleting Comments

The code generated by yiic for updating and deleting comments remains largely un-changed

432 Approving Comments

When comments are newly created they are in pending approval status and need to beapproved in order to be visible to guest users Approving a comment is mainly aboutchanging the status column of the comment

We create an actionApprove() method in CommentController as follows

public function actionApprove()

if(Yiiapp()-gtrequest-gtisPostRequest)

$comment=$this-gtloadModel()

$comment-gtapprove()

$this-gtredirect(array(rsquoindexrsquo))

else

throw new CHttpException(400rsquoInvalid requestrsquo)

In the above when the approve action is invoked via a POST request we call the approve()

method defined in the Comment model to change the status We then redirect the userbrowser to the page displaying the post that this comment belongs to

We also modify the actionIndex() method of Comment to show all comments We wouldlike to see comments pending approved to show up first

public function actionIndex()

38 4 Comment Management

$dataProvider=new CActiveDataProvider(rsquoCommentrsquo array(

rsquocriteriarsquo=gtarray(

rsquowithrsquo=gtrsquopostrsquo

rsquoorderrsquo=gtrsquotstatus tcreate time DESCrsquo

)

))

$this-gtrender(rsquoindexrsquoarray(

rsquodataProviderrsquo=gt$dataProvider

))

Notice that in the above code because both tbl post and tbl comment have columns statusand create time we need to disambiguate the corresponding column reference by prefixingthem with table alias names As described in the guide the alias for the primary table ina relational query is always t Therefore we are prefixing t to the status and create time

columns in the above code

Like the post index view the index view for CommentController uses [CListView] to displaythe comment list which in turn uses the partial view wwwrootblogprotectedviews

comment viewphp to display the detail of each individual comment We will not go intodetails here Interested readers may refer to the corresponding file in the blog demowwwrootyiidemosblogprotectedviewscomment viewphp

Chapter 5

Portlets

51 Customizing Page Layout

Features like rdquothe most recent commentsrdquo rdquotag cloudrdquo are better to be implemented inportlets A portlet is a pluggable user interface component that renders a fragment ofHTML code In this section we describe how to set up the portlet architecture for ourblog application

Before we go into details about developing portlets we first need to adjust our page layoutso that the portlets can be placed in a side bar

A page layout determines how to share common sections (eg header footer) amongdifferent pages and how to embed contents that are specific for different pages In Yiia page layout is represented as a view under the protectedviewslayouts folder Whenwe call the render() method in a controller the rendering result will be automaticallyembedded into this layout view before it is displayed to end users The default layoutview is named as main which means the wwwrootblogprotectedviewslayoutsmain

php file in our blog application

We could directly modify the main layout to add a side bar However doing so wouldmake some irrelevant pages such as about contact pages to have a side bar which is notalways desirable For this reason we need different layouts for different pages

We thus create two different layouts column1 and column2 The former displays the pagein a single column while the latter displays two columns in the page one of which is theside bar We will use column2 layout for the post and comment pages and column1 forthe rest of the pages Because both column1 and column2 still share the same header andfooter sections we let them to share the main layout

Based on the above description we create the column1 layout as follows

ltphp $this-gtbeginContent(rsquolayoutsmainrsquo) gt

ltdiv class=containergt

40 5 Portlets

ltdiv id=contentgt

ltphp echo $content gt

ltdivgtlt-- content --gt

ltdivgt

ltphp $this-gtendContent() gt

and the column2 layout as follows

ltphp $this-gtbeginContent(rsquolayoutsmainrsquo) gt

ltdiv class=containergt

ltdiv class=span-18gt

ltdiv id=contentgt

ltphp echo $content gt

ltdivgtlt-- content --gt

ltdivgt

ltdiv class=span-6 lastgt

ltdiv id=sidebargt

portlets to be placed here

ltdivgtlt-- sidebar --gt

ltdivgt

ltdivgt

ltphp $this-gtendContent() gt

As we can see both column1 and column2 are enclosed in a pair of beginContent andendContent calls They essentially use the specified view layoutsmain to decorate theenclosed content In all these layout views the variable $content refers to the dynamiccontent to be embedded into the layout

Besides customizing the layout view file we also need to adjust the CSS file wwwroot

blogcssmaincss so that the overall appearance would look like what we see in the blogdemo We will not go into details here

Info The skeleton application generated by the yiic tool uses the BluePrint CSSframework It provides a nice font reset and a robust grid system For more detailson how to use this framework please refer to its documentation

After creating the new layouts we need to set the layout property of controllers to makeuse of them So far we have three controllers SiteController PostController andCommentController We set SiteController to use column1 as the default layout whilethe rest two of the controllers use column2 for example

class PostController extends Controller

52 Creating User Menu Portlet 41

public $layout=rsquocolumn2rsquo

Tip If some view of the controller does not use the same layout as declaredby the layout property it may customize it in the view by a simple assignment$this-gtlayout=rsquonewlayoutrsquo

52 Creating User Menu Portlet

Based on the requirements analysis we need three different portlets the rdquouser menurdquoportlet the rdquotag cloudrdquo portlet and the rdquorecent commentsrdquo portlet We will develop theseportlets by extending the [CPortlet] widget provided by Yii

In this section we will develop our first concrete portlet - the user menu portlet whichdisplays a list of menu items that are only available to authenticated users The menucontains four items

bull Approve Comments a hyperlink that leads to a list of comments pending approval

bull Create New Post a hyperlink that leads to the post creation page

bull Manage Posts a hyperlink that leads to the post management page

bull Logout a link button that would log out the current user

521 Creating UserMenu Class

We create the UserMenu class to represent the logic part of the user menu portlet Theclass is saved in the file wwwrootblogprotectedcomponentsUserMenuphp which has thefollowing content

Yiiimport(rsquoziiwidgetsCPortletrsquo)

class UserMenu extends CPortlet

public function init()

$this-gttitle=CHtmlencode(Yiiapp()-gtuser-gtname)

parentinit()

42 5 Portlets

protected function renderContent()

$this-gtrender(rsquouserMenursquo)

The UserMenu class extends from the CPortlet class that we created previously It overridesboth the init() method and the renderContent() method of CPortlet The former setsthe portlet title to be the name of the current user the latter generates the portlet bodycontent by rendering a view named userMenu

Tip Notice that we have to explicitly include the CPortlet class by calling Yii

import() before we refer to it the first time This is because CPortlet is part of thezii project ndash the official extension library for Yii For performance considerationclasses in this project are not listed as core classes Therefore we have to import itbefore we use it the first time

522 Creating userMenu View

Next we create the userMenu view which is saved in the file wwwrootblogprotected

componentsviewsuserMenuphp

ltulgt

ltligtltphp echo CHtmllink(rsquoCreate New Postrsquoarray(rsquopostcreatersquo)) gtltligt

ltligtltphp echo CHtmllink(rsquoManage Postsrsquoarray(rsquopostadminrsquo)) gtltligt

ltligtltphp echo CHtmllink(rsquoApprove Commentsrsquoarray(rsquocommentindexrsquo))

rsquo (rsquo Commentmodel()-gtpendingCommentCount rsquo)rsquo gtltligt

ltligtltphp echo CHtmllink(rsquoLogoutrsquoarray(rsquositelogoutrsquo)) gtltligt

ltulgt

Info By default view files for a widget should be placed under the views sub-directory of the directory containing the widget class file The file name must bethe same as the view name

523 Using UserMenu Portlet

It is time for us to make use of our newly completed UserMenu portlet We modify thelayout view file wwwrootblogprotectedviewslayoutscolumn2php as follows

53 Creating Tag Cloud Portlet 43

ltdiv id=sidebargt

ltphp if(Yiiapp()-gtuser-gtisGuest) $this-gtwidget(rsquoUserMenursquo) gt

ltdivgt

In the above we call the widget() method to generate and execute an instance of theUserMenu class Because the portlet should only be displayed to authenticated users weonly call widget() when the isGuest property of the current user is false (meaning theuser is authenticated)

524 Testing UserMenu Portlet

Letrsquos test what we have so far

1 Open a browser window and enter the URL httpwwwexamplecomblogindex

php Verify that there is nothing displayed in the side bar section of the page

2 Click on the Login hyperlink and fill out the login form to login If successful verifythat the UserMenu portlet appears in the side bar and the portlet has the usernameas its title

3 Click on the rsquoLogoutrsquo hyperlink in the UserMenu portlet Verify that the logout actionis successful and the UserMenu portlet disappears

525 Summary

What we have created is a portlet that is highly reusable We can easily reuse it in adifferent project with little or no modification Moreover the design of this portlet followsclosely the philosophy that logic and presentation should be separated While we did notpoint this out in the previous sections such practice is used nearly everywhere in a typicalYii application

53 Creating Tag Cloud Portlet

Tag cloud displays a list of post tags with visual decorations hinting the popularity of eachindividual tag

531 Creating TagCloud Class

We create the TagCloud class in the file wwwrootblogprotectedcomponentsTagCloud

php The file has the following content

44 5 Portlets

Yiiimport(rsquoziiwidgetsCPortletrsquo)

class TagCloud extends CPortlet

public $title=rsquoTagsrsquo

public $maxTags=20

protected function renderContent()

$tags=Tagmodel()-gtfindTagWeights($this-gtmaxTags)

foreach($tags as $tag=gt$weight)

$link=CHtmllink(CHtmlencode($tag) array(rsquopostindexrsquorsquotagrsquo=gt$tag))

echo CHtmltag(rsquospanrsquo array(

rsquoclassrsquo=gtrsquotagrsquo

rsquostylersquo=gtfont-size$weightpt) $link)n

Unlike the UserMenu portlet the TagCloud portlet does not use a view Instead its presen-tation is done in the renderContent() method This is because the presentation does notcontain much HTML tags

We display each tag as a hyperlink to the post index page with the corresponding tagparameter The font size of each tag link is adjusted according to their relative weightamong other tags If a tag has higher frequency value than the other it will have a biggerfont size

532 Using TagCloud Portlet

Usage of the TagCloud portlet is very simple We modify the layout file wwwrootblog

protectedviewslayoutsmainphp as follows

ltdiv id=sidebargt

ltphp if(Yiiapp()-gtuser-gtisGuest) $this-gtwidget(rsquoUserMenursquo) gt

ltphp $this-gtwidget(rsquoTagCloudrsquo array(

rsquomaxTagsrsquo=gtYiiapp()-gtparams[rsquotagCloudCountrsquo]

)) gt

ltdivgt

54 Creating Recent Comments Portlet 45

54 Creating Recent Comments Portlet

In this section we create the last portlet that displays a list of comments recently pub-lished

541 Creating RecentComments Class

We create the RecentComments class in the file wwwrootblogprotectedcomponentsRecentCommentsphp The file has the following content

Yiiimport(rsquoziiwidgetsCPortletrsquo)

class RecentComments extends CPortlet

public $title=rsquoRecent Commentsrsquo

public $maxComments=10

public function getRecentComments()

return Commentmodel()-gtfindRecentComments($this-gtmaxComments)

protected function renderContent()

$this-gtrender(rsquorecentCommentsrsquo)

In the above we invoke the findRecentComments method which is defined in the Comment

class as follows

class Comment extends CActiveRecord

public function findRecentComments($limit=10)

return $this-gtwith(rsquopostrsquo)-gtfindAll(array(

rsquoconditionrsquo=gtrsquotstatus=rsquoselfSTATUS APPROVED

rsquoorderrsquo=gtrsquotcreate time DESCrsquo

rsquolimitrsquo=gt$limit

))

46 5 Portlets

542 Creating recentComments View

The recentComments view is saved in the file wwwrootblogprotectedcomponentsviews

recentCommentsphp It simply displays every comment returned by the RecentComments

getRecentComments() method

543 Using RecentComments Portlet

We modify the layout file wwwrootblogprotectedviewslayoutsmainphp to embedthis last portlet

ltdiv id=sidebargt

ltphp if(Yiiapp()-gtuser-gtisGuest) $this-gtwidget(rsquoUserMenursquo) gt

ltphp $this-gtwidget(rsquoTagCloudrsquo array(

rsquomaxTagsrsquo=gtYiiapp()-gtparams[rsquotagCloudCountrsquo]

)) gt

ltphp $this-gtwidget(rsquoRecentCommentsrsquo array(

rsquomaxCommentsrsquo=gtYiiapp()-gtparams[rsquorecentCommentCountrsquo]

)) gt

ltdivgt

Chapter 6

Final Work

61 Beautifying URLs

The URLs linking various pages of our blog application currently look ugly For examplethe URL for the page showing a post looks like the following

indexphpr=postshowampid=1

In this section we describe how to beautify these URLs and make them SEO-friendlyOur goal is to be able to use the following URLs in the application

1 indexphppostsyii leads to the page showing a list of posts with tag yii

2 indexphppost2A+Test+Post leads to the page showing the detail of the postwith ID 2 whose title is A Test Post

3 indexphppostupdate1 leads to the page that allows updating the post with ID1

Note that in the second URL format we include the post title in the URL This is mainlyto make the URL SEO friendly It is said that search engines may also respect the wordsfound in a URL when it is being indexed

To achieve our goal we modify the application configuration as follows

return array(

rsquocomponentsrsquo=gtarray(

rsquourlManagerrsquo=gtarray(

rsquourlFormatrsquo=gtrsquopathrsquo

rsquorulesrsquo=gtarray(

48 6 Final Work

rsquopostltidd+gtlttitlegtrsquo=gtrsquopostviewrsquorsquopostslttaggtrsquo=gtrsquopostindexrsquo

)

)

)

)

In the above we configure the urlManager component by setting its urlFormat propertyto be path and adding a set of rules

The rules are used by urlManager to parse and create the URLs in the desired formatFor example the first rule says that if a URL indexphppostsyii is requested theurlManager component should be responsible to dispatch the request to the route post

index and generate a tag GET parameter with the value yii On the other hand whencreating a URL with the route postindex and parameter tag the urlManager componentwill also use this rule to generate the desired URL indexphppostsyii For this reasonwe say that urlManager is a two-way URL manager

The urlManager component can further beautify our URLs such as hiding indexphp inthe URLs appending suffix like html to the URLs We can obtain these features easilyby configuring various properties of urlManager in the application configuration For moredetails please refer to the Guide

62 Logging Errors

A production Web application often needs sophisticated logging for various events In ourblog application we would like to log the errors occurring when it is being used Sucherrors could be programming mistakes or usersrsquo misuse of the system Logging these errorswill help us to improve the blog application

We enable the error logging by modifying the application configuration as follows

return array(

rsquopreloadrsquo=gtarray(rsquologrsquo)

rsquocomponentsrsquo=gtarray(

rsquologrsquo=gtarray(

rsquoclassrsquo=gtrsquoCLogRouterrsquo

rsquoroutesrsquo=gtarray(

array(

rsquoclassrsquo=gtrsquoCFileLogRoutersquo

rsquolevelsrsquo=gtrsquoerror warningrsquo

63 Final Tune-up and Deployment 49

)

)

)

)

)

With the above configuration if an error or warning occurs detailed information will belogged and saved in a file located under the directory wwwrootblogprotectedruntime

The log component offers more advanced features such as sending log messages to a listof email addresses displaying log messages in JavaScript console window etc For moredetails please refer to the Guide

63 Final Tune-up and Deployment

We are close to finish our blog application Before deployment we would like to do sometune-ups

631 Changing Home Page

We change to use the post list page as the home page We modify the application config-uration as follows

return array(

rsquodefaultControllerrsquo=gtrsquopostrsquo

)

Tip Because PostController already declares list to be its default action whenwe access the home page of the application we will see the result generated by thelist action of the post controller

632 Enabling Schema Caching

Because ActiveRecord relies on the metadata about tables to determine the column infor-mation it takes time to read the metadata and analyze it This may not be a problemduring development stage but for an application running in production mode it is a totalwaste of time if the database schema does not change Therefore we should enable theschema caching by modifying the application configuration as follows

50 6 Final Work

return array(

rsquocomponentsrsquo=gtarray(

rsquocachersquo=gtarray(

rsquoclassrsquo=gtrsquoCDbCachersquo

)

rsquodbrsquo=gtarray(

rsquoclassrsquo=gtrsquosystemdbCDbConnectionrsquo

rsquoconnectionStringrsquo=gtrsquosqlitewwwrootblogprotecteddatablogdbrsquo

rsquoschemaCachingDurationrsquo=gt3600

)

)

)

In the above we first add a cache component which uses a default SQLite database as thecaching storage If our server is equipped with other caching extensions such as APCwe could change to use them as well We also modify the db component by setting itsschemaCachingDuration property to be 3600 which means the parsed database schemadata can remain valid in cache for 3600 seconds

633 Disabling Debugging Mode

We modify the entry script file wwwrootblogindexphp by removing the line definingthe constant YII DEBUG This constant is useful during development stage because it allowsYii to display more debugging information when an error occurs However when theapplication is running in production mode displaying debugging information is not agood idea because it may contain sensitive information such as where the script file islocated and the content in the file etc

634 Deploying the Application

The final deployment process manly involves copying the directory wwwrootblog to thetarget directory The following checklist shows every needed step

1 Install Yii in the target place if it is not available

2 Copy the entire directory wwwrootblog to the target place

3 Edit the entry script file indexphp by pointing the $yii variable to the new Yiibootstrap file

4 Edit the file protectedyiicphp by setting the $yiic variable to be the new Yiiyiicphp file

64 Future Enhancements 51

5 Change the permission of the directories assets and protectedruntime so that theyare writable by the Web server process

64 Future Enhancements

641 Using a Theme

Without writing any code our blog application is already themeable To use a themewe mainly need to develop the theme by writing customized view files in the theme Forexample to use a theme named classic that uses a different page layout we would createa layout view file wwwrootblogthemesclassicviewslayoutsmainphp We also needto change the application configuration to indicate our choice of the classic theme

return array(

rsquothemersquo=gtrsquoclassicrsquo

)

642 Internationalization

We may also internationalize our blog application so that its pages can be displayed indifferent languages This mainly involves efforts in two aspects

First we may create view files in different languages For example for the list pageof PostController we can create a view file wwwrootblogprotectedviewspostzh cn

listphp When the application is configured to use simplified Chinese (the language codeis zh cn) Yii will automatically use this new view file instead of the original one

Second we may create message translations for those messages generated by code Themessage translations should be saved as files under the directory wwwrootblogprotected

messages We also need to modify the code where we use text strings by enclosing themin the method call Yiit()

For more details about internationalization please refer to the Guide

643 Improving Performance with Cache

While the Yii framework itself is very efficient it is not necessarily true that an applicationwritten in Yii efficient There are several places in our blog application that we can improvethe performance For example the tag clould portlet could be one of the performancebottlenecks because it involves complex database query and PHP logic

52 6 Final Work

We can make use of the sophisticated caching feature provided by Yii to improve theperformance One of the most useful components in Yii is COutputCache which caches afragment of page display so that the underlying code generating the fragment does not needto be executed for every request For example in the layout file wwwrootblogprotected

viewslayoutsmainphp we can enclose the tag cloud portlet with COutputCache

ltphp if($this-gtbeginCache(rsquotagCloudrsquo array(rsquodurationrsquo=gt3600))) gt

ltphp $this-gtwidget(rsquoTagCloudrsquo array(

rsquomaxTagsrsquo=gtYiiapp()-gtparams[rsquotagCloudCountrsquo]

)) gt

ltphp $this-gtendCache() gt

With the above code the tag cloud display will be served from cache instead of beinggenerated on-the-fly for every request The cached content will remain valid in cache for3600 seconds

644 Adding New Features

Our blog application only has very basic functionalities To become a complete blogsystem more features are needed for example calendar portlet email notifications postcategorization archived post portlet and so on We will leave the implementation of thesefeatures to interested readers

  • Contents
  • License
  • Getting Started
    • Building a Blog System using Yii
    • Testdriving with Yii
      • Installing Yii
      • Creating Skeleton Application
      • Application Workflow
        • Requirements Analysis
        • Overall Design
          • Initial Prototyping
            • Setting Up Database
              • Creating Database
              • Establishing Database Connection
                • Scaffolding
                • Authenticating User
                • Summary
                  • Post Management
                    • Customizing Post Model
                      • Customizing rules() Method
                      • Customizing relations() Method
                      • Representing Status in Text
                        • Creating and Updating Posts
                          • Customizing Access Control
                          • Customizing create and update Operations
                            • Displaying Posts
                              • Customizing view Operation
                              • Customizing index Operation
                                • Managing Posts
                                  • Listing Posts in Tabular View
                                  • Deleting Posts
                                      • Comment Management
                                        • Customizing Comment Model
                                          • Customizing rules() Method
                                          • Customizing attributeLabels() Method
                                          • Customizing Saving Process
                                            • Creating and Displaying Comments
                                              • Displaying Comments
                                              • Creating Comments
                                                • Managing Comments
                                                  • Updating and Deleting Comments
                                                  • Approving Comments
                                                      • Portlets
                                                        • Customizing Page Layout
                                                        • Creating User Menu Portlet
                                                          • Creating UserMenu Class
                                                          • Creating userMenu View
                                                          • Using UserMenu Portlet
                                                          • Testing UserMenu Portlet
                                                          • Summary
                                                            • Creating Tag Cloud Portlet
                                                              • Creating TagCloud Class
                                                              • Using TagCloud Portlet
                                                                • Creating Recent Comments Portlet
                                                                  • Creating RecentComments Class
                                                                  • Creating recentComments View
                                                                  • Using RecentComments Portlet
                                                                      • Final Work
                                                                        • Beautifying URLs
                                                                        • Logging Errors
                                                                        • Final Tune-up and Deployment
                                                                          • Changing Home Page
                                                                          • Enabling Schema Caching
                                                                          • Disabling Debugging Mode
                                                                          • Deploying the Application
                                                                            • Future Enhancements
                                                                              • Using a Theme
                                                                              • Internationalization
                                                                              • Improving Performance with Cache
                                                                              • Adding New Features
Page 17: Yii Blog 1.1.0 Demo Tutor

10 2 Initial Prototyping

return array(

rsquocomponentsrsquo=gtarray(

rsquodbrsquo=gtarray(

rsquoconnectionStringrsquo=gtrsquosqlitewwwrootblogprotecteddatablogdbrsquo

rsquotablePrefixrsquo=gtrsquotbl rsquo

)

)

)

The above configuration says that we have a db application component whose connectionStringproperty should be initialized as sqlitewwwrootblogprotecteddatablogdb and whosetablePrefix property should be tbl

With this configuration we can access the DB connection object using Yiiapp()-gtdb

at any place in our code Note that Yiiapp() returns the application instance that wecreate in the entry script If you are interested in possible methods and properties that theDB connection has you may refer to its class reference However in most cases we are notgoing to use this DB connection directly Instead we will use the so-called ActiveRecordto access the database

We would like to explain a bit more about the tablePrefix property that we set in theconfiguration This tells the db connection that it should respect the fact we are usingtbl as the prefix to our database table names In particular if in a SQL statement thereis a token enclosed within double curly brackets (eg post then the db connectionshould translate it into a name with the table prefix (eg tbl post) before sending it toDBMS for execution This feature is especially useful if in future we need to modify thetable name prefix without touching our source code For example if we are developing ageneric content management system (CMS) we may exploit this feature so that when itis being installed in a new environment we can allow users to choose a table prefix theylike

22 Scaffolding 11

Tip If you want to use MySQL instead of SQLite to store data you may cre-ate a MySQL database named blog using the SQL statements in wwwrootyii

demosblogprotecteddataschemamysqlsql Then modify the applicationconfiguration as follows

return array(

rsquocomponentsrsquo=gtarray(

rsquodbrsquo=gtarray(

rsquoconnectionStringrsquo =gt rsquomysqlhost=localhostdbname=blogrsquo

rsquoemulatePreparersquo =gt true

rsquousernamersquo =gt rsquorootrsquo

rsquopasswordrsquo =gt rsquorsquo

rsquocharsetrsquo =gt rsquoutf8rsquo

rsquotablePrefixrsquo =gt rsquotbl rsquo

)

)

)

22 Scaffolding

Create read update and delete (CRUD) are the four basic operations of data objects in anapplication Because the task of implementing the CRUD operations is so common whendeveloping Web applications Yii provides a tool to automate this process (also known asscaffolding) In this section we will describe how to use the tool to implement CRUD forposts and comments

Open a command window and run the following commands

wwwrootyiiframeworkyiic shell wwwrootblogindexphp

Yii Interactive Tool v11

Please type rsquohelprsquo for help Type rsquoexitrsquo to quit

gtgt model

gtgt crud Post

gtgt crud Comment

gtgt exit

12 2 Initial Prototyping

Info Some PHP installations may use a different phpini file for command line(CLI) PHP parser As a result when running the above yiic commands youmay encounter errors like rdquoYiiBaseinclude(PDOphp) failed to open streamrdquoor rdquocould not find driverrdquo Please double check your CLI PHP configuration byexecuting the following command

php -r phpinfo()

The result of the above command will show which phpini file is being used andwhich extensions are loaded If a wrong phpini file is used you may use thefollowing command to explicitly specify the correct phpini to use

php -c phpini wwwrootyiiframeworkyiicphp shell wwwrootblogindexphp

The commands above accomplish two tasks First the model command generates a modelclass file for each table in the blog database Second the crud commands generate thecode needed by the CRUD operations for the Post and Comment models

Tip The command model generates a model class for every table in the databaseSometimes we probably donrsquot want to do so (eg the database contains some irrel-evant tables) In this case we can create model classes one by one For exampleto create the User model we can use the command model User The model com-mand also has some more advanced usages For more details execute help model

command

We can test the generated code by accessing the following URLs

httpwwwexamplecomblogindexphpr=post

httpwwwexamplecomblogindexphpr=comment

Notice that the post and comment features implemented by the generated code are com-pletely independent of each other Also when creating a new post or comment we arerequired to enter information such as author id and create time which in real applica-tion should be set by the program Donrsquot worry We will fix these problems in the nextmilestones For now we should be fairly satisfied as this prototype already contains mostfeatures that we need to implement for the blog application

To prepare for the next milestones letrsquos take a closer look at the files generated by theabove commands All the files are generated under wwwrootblogprotected For conve-nience we group them into model files controller files and view files

22 Scaffolding 13

bull model files

ndash modelsUserphp contains the User class that extends from CActiveRecord andcan be used to access the tbl user database table

ndash modelsPostphp contains the Post class that extends from CActiveRecord andcan be used to access the tbl post database table

ndash modelsTagphp contains the Tag class that extends from CActiveRecord andcan be used to access the tbl tag database table

ndash modelsCommentphp contains the Comment class that extends from CActiveRe-cord and can be used to access the tbl comment database table

ndash modelsLookupphp contains the Lookup class that extends from CActiveRecordand can be used to access the tbl lookup database table

bull controller file

ndash controllersPostControllerphp contains the PostController class which is thecontroller in charge of all CRUD operations about posts

ndash controllersCommentControllerphp contains the CommentController class whichis the controller in charge of all CRUD operations about comments

bull view files

ndash viewspostcreatephp is the view file that shows an HTML form to create anew post

ndash viewspostupdatephp is the view file that shows an HTML form to update anexisting post

ndash viewspostviewphp is the view file that displays the detailed information of apost

ndash viewspostindexphp is the view file that displays a list of posts

ndash viewspostadminphp is the view file that displays posts in a table with ad-ministrative commands

ndash viewspost formphp is the partial view file embedded in viewspostcreate

php and viewspostupdatephp It displays the HTML form for collecting postinformation

ndash viewspost viewphp is the partial view file used by viewspostindexphp Itdisplays the brief view of a single post

ndash a similar set of view files are also generated for comment

14 2 Initial Prototyping

In order to understand better how the above files are used we show in the following theworkflow that occurs in the blog application when displaying a list of posts

1 The user requests the URL httpwwwexamplecomblogindexphpr=posts

2 The entry script is executed by the Web server which creates and initializes anapplication instance to handle the request

3 The application creates an instance of PostController and executes it

4 The PostController instance executes the index action by calling its actionIndex()

method Note that index is the default action if the user does not specify an actionto execute in the URL

5 The actionIndex() method queries database to bring back the list of recent posts

6 The actionIndex() method renders the index view with the post data

23 Authenticating User

Our blog application needs to differentiate between the system owner and guest usersTherefore we need to implement the user authentication feature

As you may have found that the skeleton application already provides user authenticationby checking if the username and password are both demo or admin In this section wewill modify the corresponding code so that the authentication is done against the User

database table

User authentication is performed in a class implementing the IUserIdentity interface Theskeleton application uses the UserIdentity class for this purpose The class is stored inthe file wwwrootblogprotectedcomponentsUserIdentityphp

Tip By convention the name of a class file must be the same as the correspondingclass name suffixed with the extension php Following this convention one canrefer to a class using a path alias For example we can refer to the UserIdentity

class with the alias applicationcomponentsUserIdentity Many APIs in Yiican recognize path aliases (eg YiicreateComponent()) and using path aliasesavoids the necessity of embedding absolute file paths in the code The existence ofthe latter often causes trouble when we deploy an application

We modify the UserIdentity class as follows

23 Authenticating User 15

ltphp

class UserIdentity extends CUserIdentity

private $ id

public function authenticate()

$username=strtolower($this-gtusername)

$user=Usermodel()-gtfind(rsquoLOWER(username)=rsquoarray($username))

if($user===null)

$this-gterrorCode=selfERROR USERNAME INVALID

else if($user-gtvalidatePassword($this-gtpassword))

$this-gterrorCode=selfERROR PASSWORD INVALID

else

$this-gt id=$user-gtid

$this-gtusername=$user-gtusername

$this-gterrorCode=selfERROR NONE

return $this-gterrorCode==selfERROR NONE

public function getId()

return $this-gt id

In the authenticate() method we use the User class to look for a row in the User tablewhose username column is the same as the given username in a case-insensitive mannerRemember that the User class was created using the yiic tool in the prior section Becausethe User class extends from CActiveRecord we can exploit the ActiveRecord feature toaccess the User table in an OOP fashion

In order to check if the user has entered a valid password we invoke the validatePassword

method of the User class We need to modify the file wwwrootblogprotectedmodels

Userphp as follows Note that instead of storing the plain password in the database westore the hash result of the password and a randomly generated salt key When validatingthe user-entered password we should compare the hash results instead

class User extends CActiveRecord

public function validatePassword($password)

return $this-gthashPassword($password$this-gtsalt)===$this-gtpassword

16 2 Initial Prototyping

public function hashPassword($password$salt)

return md5($salt$password)

In the UserIdentity class we also override the getId() method which returns the id valueof the user found in the User table The parent implementation would return the usernameinstead Both the username and id properties will be stored in the user session and maybe accessed via Yiiapp()-gtuser from anywhere in our code

Tip In the UserIdentity class we reference the class CUserIdentity without ex-plicitly including the corresponding class file This is because CUserIdentity is acore class provided by the Yii framework Yii will automatically include the classfile for any core class when it is referenced for the first time

We also do the same with the User class This is because the User class file is placedunder the directory wwwrootblogprotectedmodels which has been added tothe PHP include path according to the following lines found in the applicationconfiguration

return array(

rsquoimportrsquo=gtarray(

rsquoapplicationmodelsrsquo

rsquoapplicationcomponentsrsquo

)

)

The above configuration says that any class whose class file is located under either wwwrootblogprotectedmodels or wwwrootblogprotectedcomponents willbe automatically included when the class is referenced for the first time

The UserIdentity class is mainly used by the LoginForm class to authenticate a user basedon the username and password input collected from the login page The following codefragment shows how UserIdentity is used

$identity=new UserIdentity($username$password)

$identity-gtauthenticate()

switch($identity-gterrorCode)

24 Summary 17

case UserIdentityERROR NONE

Yiiapp()-gtuser-gtlogin($identity)

break

Info People often get confused about identity and the user application componentThe former represents a way of performing authentication while the latter is usedto represent the information related with the current user An application can onlyhave one user component but it can have one or several identity classes dependingon what kind of authentication it supports Once authenticated an identity instancemay pass its state information to the user component so that they are globallyaccessible via user

To test the modified UserIdentity class we can browse the URL httpwwwexample

comblogindexphp and try logging in with the username and password that we store inthe User table If we use the database provided by the blog demo we should be able tologin with username demo and password demo Note that this blog system does not providethe user management feature As a result a user cannot change his account or create anew one through the Web interface The user management feature may be considered asa future enhancement to the blog application

24 Summary

We have completed the milestone 1 Letrsquos summarize what we have done so far

1 We identified the requirements to be fulfilled

2 We installed the Yii framework

3 We created a skeleton application

4 We designed and created the blog database

5 We modified the application configuration by adding the database connection

6 We generated the code that implements the basic CRUD operations for both postsand comments

7 We modified the authentication method to check against the tbl user table

18 2 Initial Prototyping

For a new project most of the time will be spent in step 1 and 4 for this first milestone

Although the code generated by the yiic tool implements fully functional CRUD oper-ations for a database table it often needs to be modified in practical applications Forthis reason in the next two milestone our job is to customize the generated CRUD codeabout posts and comments so that it reaches our initial requirements

In general we first modify the model class file by adding appropriate validation rules anddeclaring relational objects We then modify the controller action and view code for eachindividual CRUD operation

Chapter 3

Post Management

31 Customizing Post Model

The Post model class generated by the yiic tool mainly needs to be modified in two places

bull the rules() method specifies the validation rules for the model attributes

bull the relations() method specifies the related objects

Info A model consists of a list of attributes each associated with a column in thecorresponding database table Attributes can be declared explicitly as class membervariables or implicitly without any declaration

311 Customizing rules() Method

We first specify the validation rules which ensure the attribute values entered by usersare correct before they are saved to the database For example the status attribute ofPost should be an integer 1 2 or 3 The yiic tool also generates validation rules for eachmodel However these rules are based on the table column information and may not beappropriate

Based on the requirement analysis we modify the rules() method as follows

public function rules()

return array(

array(rsquotitle content statusrsquo rsquorequiredrsquo)

array(rsquotitlersquo rsquolengthrsquo rsquomaxrsquo=gt128)

array(rsquostatusrsquo rsquoinrsquo rsquorangersquo=gtarray(123))

array(rsquotagsrsquo rsquomatchrsquo rsquopatternrsquo=gtrsquo^[ws]+$rsquorsquomessagersquo=gtrsquoTags can only contain word charactersrsquo)

20 3 Post Management

array(rsquotagsrsquo rsquonormalizeTagsrsquo)

)

In the above we specify that the title content and status attributes are required thelength of title should not exceed 128 the status attribute value should be 1 (draft) 2(published) or 3 (archived) and the tags attribute should only contain word charactersand commas In addition we use normalizeTags to normalize the user-entered tags so thatthe tags are unique and properly separated with commas

The validators such as required length in and match are all built-in validators providedby Yii The normalizeTags validator is a method-based validator that we need to definein the Post class For more information about how to specify validation rules please referto the Guide

public function normalizeTags($attribute$params)

$this-gttags=Tagarray2string(array unique(Tagstring2array($this-gttags)))

The rules declared in this method are executed one by one when we call the validate() orsave() method of the model instance

Note It is very important to remember that attributes appearing in rules() mustbe those to be entered by end users Other attributes such as id and create time

in the Post model which are set by our code or database should not be in rules()For more details please refer to Securing Attribute Assignments

After making these changes we can visit the post creation page again to verify that thenew validation rules are taking effect

312 Customizing relations() Method

Lastly we customize the relations() method to specify the related objects of a post Bydeclaring these related objects in relations() we can exploit the powerful RelationalActiveRecord (RAR) feature to access the related object information of a post such asits author and comments without the need to write complex SQL JOIN statements

We customize the relations() method as follows

31 Customizing Post Model 21

public function relations()

return array(

rsquoauthorrsquo =gt array(selfBELONGS TO rsquoUserrsquo rsquoauthor idrsquo)

rsquocommentsrsquo =gt array(selfHAS MANY rsquoCommentrsquo rsquopost idrsquo

rsquoconditionrsquo=gtrsquocommentsstatus=rsquoCommentSTATUS APPROVED

rsquoorderrsquo=gtrsquocommentscreate time DESCrsquo)

rsquocommentCountrsquo =gt array(selfSTAT rsquoCommentrsquo rsquopost idrsquo

rsquoconditionrsquo=gtrsquostatus=rsquoCommentSTATUS APPROVED)

)

The above relations state that

bull A post belongs to an author whose class is User and the relationship is establishedbased on the author id attribute value of the post

bull A post has many comments whose class is Comment and the relationship is establishedbased on the post id attribute value of the comments These comments should besorted according to their creation time and the comments must be approved

bull The commentCount relation is a bit special as it returns back an aggregation resultwhich is about how many comments the post has

With the above relation declaration we can easily access the author and comments of apost like the following

$author=$post-gtauthor

echo $author-gtusername

$comments=$post-gtcomments

foreach($comments as $comment)

echo $comment-gtcontent

For more details about how to declare and use relations please refer to the Guide

313 Representing Status in Text

Because the status of a post is stored as an integer in the database we need to provide atextual representation so that it is more intuitive when being displayed to end users In alarge system the similar requirement is very common

22 3 Post Management

As a generic solution we use the tbl lookup table to store the mapping between integervalues and textual representations that are needed by other data objects We modify theLookup model class as follows to more easily access the textual data in the table

class Lookup extends CActiveRecord

private static $ items=array()

public static function items($type)

if(isset(self$ items[$type]))

selfloadItems($type)

return self$ items[$type]

public static function item($type$code)

if(isset(self$ items[$type]))

selfloadItems($type)

return isset(self$ items[$type][$code]) self$ items[$type][$code] false

private static function loadItems($type)

self$ items[$type]=array()

$models=selfmodel()-gtfindAll(array(

rsquoconditionrsquo=gtrsquotype=typersquo

rsquoparamsrsquo=gtarray(rsquotypersquo=gt$type)

rsquoorderrsquo=gtrsquopositionrsquo

))

foreach($models as $model)

self$ items[$type][$model-gtcode]=$model-gtname

Our new code mainly provides two static methods Lookupitems() and Lookupitem()The former returns a list of strings belonging to the specified data type while the latterreturns a particular string for the given data type and data value

Our blog database is pre-populated with two lookup types PostStatus and CommentStatusThe former refers to the possible post statuses while the latter the comment statuses

In order to make our code easier to read we also declare a set of constants to representthe status integer values We should use these constants through our code when referringto the corresponding status values

32 Creating and Updating Posts 23

class Post extends CActiveRecord

const STATUS DRAFT=0

const STATUS PUBLISHED=1

const STATUS ARCHIVED=2

Therefore we can call Lookupitems(rsquoPostStatusrsquo) to get the list of possible post statuses(text strings indexed by the corresponding integer values) and call Lookupitem(rsquoPostStatusrsquoPostSTATUS PUBLISHED) to get the string representation of the published status

32 Creating and Updating Posts

With the Post model ready we need to fine-tune the actions and views for the controllerPostController In this section we first customize the access control of CRUD operationswe then modify the code implementing the create and update operations

321 Customizing Access Control

The first thing we want to do is to customize the access control because the code generatedby yiic does not fit our needs

We modify the accessRules() method in the file wwwrootblogprotectedcontrollers

PostControllerphp as follows

public function accessRules()

return array(

array(rsquoallowrsquo allow all users to perform rsquolistrsquo and rsquoshowrsquo actions

rsquoactionsrsquo=gtarray(rsquoindexrsquo rsquoviewrsquo)

rsquousersrsquo=gtarray(rsquorsquo)

)

array(rsquoallowrsquo allow authenticated users to perform any action

rsquousersrsquo=gtarray(rsquorsquo)

)

array(rsquodenyrsquo deny all users

rsquousersrsquo=gtarray(rsquorsquo)

)

)

The above rules state that all users can access the index and view actions and authenti-cated users can access any actions including the admin action The user should be denied

24 3 Post Management

access in any other scenario Note that these rules are evaluated in the order they arelisted here The first rule matching the current context makes the access decision Forexample if the current user is the system owner who tries to visit the post creation pagethe second rule will match and it will give the access to the user

322 Customizing create and update Operations

The create and update operations are very similar They both need to display an HTMLform to collect user inputs validate them and save them into database The main differ-ence is that the update operation will pre-populate the form with the existing post datafound in the database For this reason the yiic tool generates a partial view wwwroot

blogprotectedviewspost formphp that is embedded in both the create and update

views to render the needed HTML form

We first change the formphp file so that the HTML form only collects the inputs wewant title content tags and status We use plain text fields to collect inputs for thefirst three attributes and a dropdown list to collect input for status The dropdown listoptions are the text displays of the possible post statuses

ltphp echo CHtmlactiveDropDownList($postrsquostatusrsquoLookupitems(rsquoPostStatusrsquo)) gt

In the above we call Lookupitems(rsquoPostStatusrsquo) to bring back the list of post statuses

We then modify the Post class so that it can automatically set some attributes (eg create

time author id) before a post is saved to the database We override the beforeSave()

method as follows

protected function beforeSave()

if(parentbeforeSave())

if($this-gtisNewRecord)

$this-gtcreate time=$this-gtupdate time=time()

$this-gtauthor id=Yiiapp()-gtuser-gtid

else

$this-gtupdate time=time()

return true

else

return false

33 Displaying Posts 25

When we save a post we want to update the tbl tag table to reflect the change of tagfrequencies We can do this work in the afterSave() method which is automaticallyinvoked by Yii after a post is successfully saved into the database

protected function afterSave()

parentafterSave()

Tagmodel()-gtupdateFrequency($this-gt oldTags $this-gttags)

private $ oldTags

protected function afterFind()

parentafterFind()

$this-gt oldTags=$this-gttags

In the implementation because we want to detect if the user changes the tags in case he isupdating an existing post we need to know what the old tags are For this reason we alsowrite the afterFind() method to keep the old tags in the variable oldTags The methodafterFind() is invoked automatically by Yii when an AR record is populated with thedata from database

We are not going to give details of the TagupdateFrequency() method here Interestedreaders may refer to the file wwwrootyiidemosblogprotectedmodelsTagphp

33 Displaying Posts

In our blog application a post may be displayed among a list of posts or by itself Theformer is implemented as the index operation while the latter the view operation In thissection we customize both operations to fulfill our initial requirements

331 Customizing view Operation

The view operation is implemented by the actionView() method in PostController Itsdisplay is generated by the view view with the view file wwwrootblogprotectedviews

postviewphp

Below is the relevant code implementing the view operation in PostController

public function actionView()

26 3 Post Management

$this-gtrender(rsquoviewrsquoarray(

$post=$this-gtloadModel()

$this-gtrender(rsquoviewrsquoarray(

rsquomodelrsquo=gt$post

))

))

private $ model

public function loadModel()

if($this-gt model===null)

if(isset($ GET[rsquoidrsquo]))

if(Yiiapp()-gtuser-gtisGuest)

$condition=rsquostatus=rsquoPostSTATUS PUBLISHED

rsquo OR status=rsquoPOSTSTATUS ARCHIVED

else

$condition=rsquorsquo

$this-gt model=Postmodel()-gtfindbyPk($ GET[rsquoidrsquo] $condition)

if($this-gt model===null)

throw new CHttpException(404rsquoThe requested page does not existrsquo)

return $this-gt model

Our change mainly lies in the loadModel() method In this method we query the Post

table according to the id GET parameter If the post is not found or if it is not publishedor archived (when the user is a guest) we will throw a 404 HTTP error Otherwise thepost object is returned to actionView() which in turn passes the post object to the viewscript for further display

Tip Yii captures HTTP exceptions (instances of CHttpException) and displaysthem in either predefined templates or customized error views The skeleton appli-cation generated by yiic already contains a customized error view in wwwroot

blogprotectedviewssiteerrorphp We can modify this file if we want tofurther customize the error display

The change in the view script is mainly about ajdusting the formatting and styles of thepost display We will not go into details here Interested readers may refer to wwwroot

blogprotectedviewspostviewphp

33 Displaying Posts 27

332 Customizing index Operation

Like the view operation we customize the index operation in two places the actionIndex()

method in PostController and the view file wwwrootblogprotectedviewspostindex

php We mainly need to add the support for displaying a list of posts that are associatedwith a specified tag

Below is the modified actionIndex() method in PostController

public function actionIndex()

$criteria=new CDbCriteria(array(

rsquoconditionrsquo=gtrsquostatus=rsquoPostSTATUS PUBLISHED

rsquoorderrsquo=gtrsquoupdate time DESCrsquo

rsquowithrsquo=gtrsquocommentCountrsquo

))

if(isset($ GET[rsquotagrsquo]))

$criteria-gtaddSearchCondition(rsquotagsrsquo$ GET[rsquotagrsquo])

$dataProvider=new CActiveDataProvider(rsquoPostrsquo array(

rsquopaginationrsquo=gtarray(

rsquopageSizersquo=gtYiiapp()-gtparams[rsquopostsPerPagersquo]

)

rsquocriteriarsquo=gt$criteria

))

$this-gtrender(rsquoindexrsquoarray(

rsquodataProviderrsquo=gt$dataProvider

))

In the above we first create a query criteria for retrieving post list The criteria statesthat only published posts should be returned and they should be sorted according to theirupdate time in descending order Because when displaying a post in the list we want toshow how many comments the post has received in the criteria we also specify to bringback commentCount which if you remember is a relation declared in Postrelations()

In case when a user wants to see posts with a specific tag we would add a search conditionto the criteria to look for the specified tag

Using the query criteria we create a data provider which mainly serves for three purposesFirst it does pagination of the data when too many results may be returned Herewe customize the pagination by setting the page size to be an application parameterpostsPerPage Second it does sorting according to the user request And finally it feedsthe paginated and sorted data to widgets or view code for presentation

28 3 Post Management

After we finish with actionIndex() we modify the index view as follows Our change ismainly about adding the h1 header when the user specifies to display posts with a tag

ltphp if(empty($ GET[rsquotagrsquo])) gt

lth1gtPosts Tagged with ltigtltphp echo CHtmlencode($ GET[rsquotagrsquo]) gtltigtlth1gt

ltphp endif gt

ltphp $this-gtwidget(rsquoziiwidgetsCListViewrsquo array(

rsquodataProviderrsquo=gt$dataProvider

rsquoitemViewrsquo=gtrsquo viewrsquo

rsquotemplatersquo=gtitemsnpager)) gt

Note that in the above we use [CListView] to display the post list This widget requires apartial view to display the detail of each individual post Here we specify the partial viewto be view which means the file wwwrootblogprotectedviewspost viewphp In thisview script we can acccess the post instance being displayed via a local variable named$data

34 Managing Posts

Managing posts mainly refers to listing posts in an administrative view that allows us tosee posts with all statuses updating them and deleting them They are accomplished bythe admin operation and the delete operation respectively The code generated by yiic

does not need much modification Below we mainly explain how these two operations areimplemented

341 Listing Posts in Tabular View

The admin operation shows posts with all statuses in a tabular view The view supportssorting and pagination The following is the actionAdmin() method in PostController

public function actionAdmin()

$dataProvider=new CActiveDataProvider(rsquoPostrsquo array(

rsquosortrsquo=gtarray(

rsquodefaultOrderrsquo=gtrsquostatus update time DESCrsquo

)

))

$this-gtrender(rsquoadminrsquoarray(

rsquodataProviderrsquo=gt$dataProvider

))

34 Managing Posts 29

The above code is very similar to that in actionIndex() The main difference is in thequery criteria Here we do not specify any particular query criteria since we want to seeall posts We do customize the sort property of the data provider so that by default theposts are shown in the order of their statuses and update time

Below is the code for the admin view

lth1gtManage Postslth1gt

ltphp $this-gtwidget(rsquoziiwidgetsgridCGridViewrsquo array(

rsquodataProviderrsquo=gt$dataProvider

rsquocolumnsrsquo=gtarray(

array(

rsquonamersquo=gtrsquotitlersquo

rsquotypersquo=gtrsquorawrsquo

rsquovaluersquo=gtrsquoCHtmllink(CHtmlencode($data-gttitle) $data-gturl)rsquo

)

array(

rsquonamersquo=gtrsquostatusrsquo

rsquovaluersquo=gtrsquoLookupitem(PostStatus$data-gtstatus)rsquo

)

rsquocreate timedatetimersquo

rsquoupdate timedatetimersquo

array(

rsquoclassrsquo=gtrsquoCButtonColumnrsquo

)

)

)) gt

We use [CGridView] to display the posts It allows us to sort by a column and paginatethrough the posts if there are too many to be displayed in a single page Our change ismainly about how to display each column For example for the title column we specifythat it should be displayed as a hyperlink that points to the detailed view of the post

Tip When displaying text we call CHtmlencode() to encode HTML entities init This prevents from cross-site scripting attack

342 Deleting Posts

In the admin data grid there is a delete button in each row Clicking on the button shoulddelete the corresponding post Internally this triggers the delete action implemented asfollows

30 3 Post Management

public function actionDelete()

if(Yiiapp()-gtrequest-gtisPostRequest)

we only allow deletion via POST request

$this-gtloadModel()-gtdelete()

if(isset($ POST[rsquoajaxrsquo]))

$this-gtredirect(array(rsquoindexrsquo))

else

throw new CHttpException(400rsquoInvalid request Please do not repeat this request againrsquo)

The above code is the one generated by the yiic tool without any change We wouldlike to explain a little bit more about the checking on $ POST[rsquoajaxrsquo] The [CGridView]widget has a very nice feature that its sorting pagination and deletion operations are alldone in AJAX mode by default That means the whole page does not get reloaded if anyof the above operations is performed However it is also possible that the widget runs innon-AJAX mode (by setting its ajaxUpdate property to be false or disabling JavaScript onthe client side) It is necessary for the delete action to differentiate these two scenarios ifthe delete request is made via AJAX we should not redirect the user browser otherwisewe should

Deleting a post should also cause the deletion of all comments for that post In additionwe should also update the tbl tag table regarding the tags for the deleted post Both ofthese tasks can be achieved by writing an afterDelete method in the Post model class asfollows

protected function afterDelete()

parentafterDelete()

Commentmodel()-gtdeleteAll(rsquopost id=rsquo$this-gtid)

Tagmodel()-gtupdateFrequency($this-gttags rsquorsquo)

The above code is very straightforward it first deletes all those comments whose post id

is the same as the ID of the deleted post it then updates the tbl tag table for the tags

of the deleted post

34 Managing Posts 31

Tip We have to explicitly delete all comments for the deleted post here becauseSQLite does not really support foreign key constraints In a DBMS that supportsthis constraint (such as MySQL PostgreSQL) the foreign key constraint can be setup such that the DBMS automatically deletes the related comments if the post isdeleted In that case we no longer this explicit deletion call in our code

32 3 Post Management

Chapter 4

Comment Management

41 Customizing Comment Model

For the Comment model we mainly need to customize the rules() and attributeLabels()

methods The attributeLabels() method returns a mapping between attribute namesand attribute labels We do not need to touch relations() since the code generated bythe yiic tool is good enough

411 Customizing rules() Method

We first customize the validation rules generated by the yiic tool The following rules areused for comments

public function rules()

return array(

array(rsquocontent author emailrsquo rsquorequiredrsquo)

array(rsquoauthor email urlrsquo rsquolengthrsquo rsquomaxrsquo=gt128)

array(rsquoemailrsquorsquoemailrsquo)

array(rsquourlrsquorsquourlrsquo)

)

In the above we specify that the author email and content attributes are required thelength of author email and url cannot exceed 128 the email attribute must be a validemail address and the url attribute must be a valid URL

412 Customizing attributeLabels() Method

We then customize the attributeLabels() method to declare the label display for eachmodel attribute This method returns an array consisting of name-label pairs When wecall CHtmlactiveLabel() to display an attribute label

34 4 Comment Management

public function attributeLabels()

return array(

rsquoidrsquo =gt rsquoIdrsquo

rsquocontentrsquo =gt rsquoCommentrsquo

rsquostatusrsquo =gt rsquoStatusrsquo

rsquocreate timersquo =gt rsquoCreate Timersquo

rsquoauthorrsquo =gt rsquoNamersquo

rsquoemailrsquo =gt rsquoEmailrsquo

rsquourlrsquo =gt rsquoWebsitersquo

rsquopost idrsquo =gt rsquoPostrsquo

)

Tip If the label for an attribute is not declared in attributeLabels() an algo-rithm will be used to generate an appropriate label For example a label VerifyCode will be generated for attributes verify code or verifyCode

413 Customizing Saving Process

Because we want to record the creation time of a comment we override the beforeSave()

method of Comment like we do for the Post model

protected function beforeSave()

if(parentbeforeSave())

if($this-gtisNewRecord)

$this-gtcreate time=time()

return true

else

return false

42 Creating and Displaying Comments

In this section we implement the comment display and creation features

421 Displaying Comments

Instead of displaying and creating comments on individual pages we use the post detailpage (generated by the view action of PostController) Below the post content display

42 Creating and Displaying Comments 35

we display first a list of comments belonging to that post and then a comment creationform

In order to display comments on the post detail page we modify the view script wwwrootblogprotectedviewspostviewphp as follows

ltdiv id=commentsgt

ltphp if($model-gtcommentCountgt=1) gt

lth3gt

ltphp echo $model-gtcommentCount rsquocomment(s)rsquo gt

lth3gt

ltphp $this-gtrenderPartial(rsquo commentsrsquoarray(

rsquopostrsquo=gt$model

rsquocommentsrsquo=gt$model-gtcomments

)) gt

ltphp endif gt

ltdivgt

In the above we call renderPartial() to render a partial view named comments to displaythe list of comments belonging to the current post Note that in the view we use theexpression $model-gtcomments to retrieve the comments for the post This is valid becausewe have declared a comments relation in the Post class Evaluating this expression wouldtrigger an implicit JOIN database query to bring back the proper comments This featureis known as lazy relational query

The partial view comments is not very interesting It mainly goes through every commentand displays the detail of it Interested readers may refer to wwwrootyiidemosblog

protectedpost commentsphp

422 Creating Comments

To handle comment creation we first modify the actionView() method of PostController

as follows

public function actionView()

$post=$this-gtloadModel()

$comment=$this-gtnewComment($post)

$this-gtrender(rsquoviewrsquoarray(

rsquomodelrsquo=gt$post

rsquocommentrsquo=gt$comment

))

36 4 Comment Management

protected function newComment($post)

$comment=new Comment

if(isset($ POST[rsquoCommentrsquo]))

$comment-gtattributes=$ POST[rsquoCommentrsquo]

if($post-gtaddComment($comment))

if($comment-gtstatus==CommentSTATUS PENDING)

Yiiapp()-gtuser-gtsetFlash(rsquocommentSubmittedrsquorsquoThank yoursquo)

$this-gtrefresh()

return $comment

In the above we call the newComment() method before we render view In the newComment()

method we generate a Comment instance and check if the comment form is submitted If sowe try to add the comment for the post by calling $post-gtaddComment($comment) If it goesthrough we refresh the post detail page In case the comment needs to be approved we willshow a flash message to indicate this decision A flash message is usually a confirmationmessage displayed to end users If the user clicks on the refresh button of his browser themessage will disappear

We also need to modify wwwrootblogprotectedviewspostviewphp furthermore

ltdiv id=commentsgt

lth3gtLeave a Commentlth3gt

ltphp if(Yiiapp()-gtuser-gthasFlash(rsquocommentSubmittedrsquo)) gt

ltdiv class=successgt

ltphp echo Yiiapp()-gtuser-gtgetFlash(rsquocommentSubmittedrsquo) gt

ltdivgt

ltphp else gt

ltphp $this-gtrenderPartial(rsquocomment formrsquoarray(

rsquomodelrsquo=gt$comment

)) gt

ltphp endif gt

ltdivgtlt-- comments --gt

43 Managing Comments 37

In the above code we display the flash message if it is available If not we displaythe comment input form by rendering the partial view wwwrootblogprotectedviews

comment formphp

43 Managing Comments

Comment management includes updating deleting and approving comments These op-erations are implemented as actions in the CommentController class

431 Updating and Deleting Comments

The code generated by yiic for updating and deleting comments remains largely un-changed

432 Approving Comments

When comments are newly created they are in pending approval status and need to beapproved in order to be visible to guest users Approving a comment is mainly aboutchanging the status column of the comment

We create an actionApprove() method in CommentController as follows

public function actionApprove()

if(Yiiapp()-gtrequest-gtisPostRequest)

$comment=$this-gtloadModel()

$comment-gtapprove()

$this-gtredirect(array(rsquoindexrsquo))

else

throw new CHttpException(400rsquoInvalid requestrsquo)

In the above when the approve action is invoked via a POST request we call the approve()

method defined in the Comment model to change the status We then redirect the userbrowser to the page displaying the post that this comment belongs to

We also modify the actionIndex() method of Comment to show all comments We wouldlike to see comments pending approved to show up first

public function actionIndex()

38 4 Comment Management

$dataProvider=new CActiveDataProvider(rsquoCommentrsquo array(

rsquocriteriarsquo=gtarray(

rsquowithrsquo=gtrsquopostrsquo

rsquoorderrsquo=gtrsquotstatus tcreate time DESCrsquo

)

))

$this-gtrender(rsquoindexrsquoarray(

rsquodataProviderrsquo=gt$dataProvider

))

Notice that in the above code because both tbl post and tbl comment have columns statusand create time we need to disambiguate the corresponding column reference by prefixingthem with table alias names As described in the guide the alias for the primary table ina relational query is always t Therefore we are prefixing t to the status and create time

columns in the above code

Like the post index view the index view for CommentController uses [CListView] to displaythe comment list which in turn uses the partial view wwwrootblogprotectedviews

comment viewphp to display the detail of each individual comment We will not go intodetails here Interested readers may refer to the corresponding file in the blog demowwwrootyiidemosblogprotectedviewscomment viewphp

Chapter 5

Portlets

51 Customizing Page Layout

Features like rdquothe most recent commentsrdquo rdquotag cloudrdquo are better to be implemented inportlets A portlet is a pluggable user interface component that renders a fragment ofHTML code In this section we describe how to set up the portlet architecture for ourblog application

Before we go into details about developing portlets we first need to adjust our page layoutso that the portlets can be placed in a side bar

A page layout determines how to share common sections (eg header footer) amongdifferent pages and how to embed contents that are specific for different pages In Yiia page layout is represented as a view under the protectedviewslayouts folder Whenwe call the render() method in a controller the rendering result will be automaticallyembedded into this layout view before it is displayed to end users The default layoutview is named as main which means the wwwrootblogprotectedviewslayoutsmain

php file in our blog application

We could directly modify the main layout to add a side bar However doing so wouldmake some irrelevant pages such as about contact pages to have a side bar which is notalways desirable For this reason we need different layouts for different pages

We thus create two different layouts column1 and column2 The former displays the pagein a single column while the latter displays two columns in the page one of which is theside bar We will use column2 layout for the post and comment pages and column1 forthe rest of the pages Because both column1 and column2 still share the same header andfooter sections we let them to share the main layout

Based on the above description we create the column1 layout as follows

ltphp $this-gtbeginContent(rsquolayoutsmainrsquo) gt

ltdiv class=containergt

40 5 Portlets

ltdiv id=contentgt

ltphp echo $content gt

ltdivgtlt-- content --gt

ltdivgt

ltphp $this-gtendContent() gt

and the column2 layout as follows

ltphp $this-gtbeginContent(rsquolayoutsmainrsquo) gt

ltdiv class=containergt

ltdiv class=span-18gt

ltdiv id=contentgt

ltphp echo $content gt

ltdivgtlt-- content --gt

ltdivgt

ltdiv class=span-6 lastgt

ltdiv id=sidebargt

portlets to be placed here

ltdivgtlt-- sidebar --gt

ltdivgt

ltdivgt

ltphp $this-gtendContent() gt

As we can see both column1 and column2 are enclosed in a pair of beginContent andendContent calls They essentially use the specified view layoutsmain to decorate theenclosed content In all these layout views the variable $content refers to the dynamiccontent to be embedded into the layout

Besides customizing the layout view file we also need to adjust the CSS file wwwroot

blogcssmaincss so that the overall appearance would look like what we see in the blogdemo We will not go into details here

Info The skeleton application generated by the yiic tool uses the BluePrint CSSframework It provides a nice font reset and a robust grid system For more detailson how to use this framework please refer to its documentation

After creating the new layouts we need to set the layout property of controllers to makeuse of them So far we have three controllers SiteController PostController andCommentController We set SiteController to use column1 as the default layout whilethe rest two of the controllers use column2 for example

class PostController extends Controller

52 Creating User Menu Portlet 41

public $layout=rsquocolumn2rsquo

Tip If some view of the controller does not use the same layout as declaredby the layout property it may customize it in the view by a simple assignment$this-gtlayout=rsquonewlayoutrsquo

52 Creating User Menu Portlet

Based on the requirements analysis we need three different portlets the rdquouser menurdquoportlet the rdquotag cloudrdquo portlet and the rdquorecent commentsrdquo portlet We will develop theseportlets by extending the [CPortlet] widget provided by Yii

In this section we will develop our first concrete portlet - the user menu portlet whichdisplays a list of menu items that are only available to authenticated users The menucontains four items

bull Approve Comments a hyperlink that leads to a list of comments pending approval

bull Create New Post a hyperlink that leads to the post creation page

bull Manage Posts a hyperlink that leads to the post management page

bull Logout a link button that would log out the current user

521 Creating UserMenu Class

We create the UserMenu class to represent the logic part of the user menu portlet Theclass is saved in the file wwwrootblogprotectedcomponentsUserMenuphp which has thefollowing content

Yiiimport(rsquoziiwidgetsCPortletrsquo)

class UserMenu extends CPortlet

public function init()

$this-gttitle=CHtmlencode(Yiiapp()-gtuser-gtname)

parentinit()

42 5 Portlets

protected function renderContent()

$this-gtrender(rsquouserMenursquo)

The UserMenu class extends from the CPortlet class that we created previously It overridesboth the init() method and the renderContent() method of CPortlet The former setsthe portlet title to be the name of the current user the latter generates the portlet bodycontent by rendering a view named userMenu

Tip Notice that we have to explicitly include the CPortlet class by calling Yii

import() before we refer to it the first time This is because CPortlet is part of thezii project ndash the official extension library for Yii For performance considerationclasses in this project are not listed as core classes Therefore we have to import itbefore we use it the first time

522 Creating userMenu View

Next we create the userMenu view which is saved in the file wwwrootblogprotected

componentsviewsuserMenuphp

ltulgt

ltligtltphp echo CHtmllink(rsquoCreate New Postrsquoarray(rsquopostcreatersquo)) gtltligt

ltligtltphp echo CHtmllink(rsquoManage Postsrsquoarray(rsquopostadminrsquo)) gtltligt

ltligtltphp echo CHtmllink(rsquoApprove Commentsrsquoarray(rsquocommentindexrsquo))

rsquo (rsquo Commentmodel()-gtpendingCommentCount rsquo)rsquo gtltligt

ltligtltphp echo CHtmllink(rsquoLogoutrsquoarray(rsquositelogoutrsquo)) gtltligt

ltulgt

Info By default view files for a widget should be placed under the views sub-directory of the directory containing the widget class file The file name must bethe same as the view name

523 Using UserMenu Portlet

It is time for us to make use of our newly completed UserMenu portlet We modify thelayout view file wwwrootblogprotectedviewslayoutscolumn2php as follows

53 Creating Tag Cloud Portlet 43

ltdiv id=sidebargt

ltphp if(Yiiapp()-gtuser-gtisGuest) $this-gtwidget(rsquoUserMenursquo) gt

ltdivgt

In the above we call the widget() method to generate and execute an instance of theUserMenu class Because the portlet should only be displayed to authenticated users weonly call widget() when the isGuest property of the current user is false (meaning theuser is authenticated)

524 Testing UserMenu Portlet

Letrsquos test what we have so far

1 Open a browser window and enter the URL httpwwwexamplecomblogindex

php Verify that there is nothing displayed in the side bar section of the page

2 Click on the Login hyperlink and fill out the login form to login If successful verifythat the UserMenu portlet appears in the side bar and the portlet has the usernameas its title

3 Click on the rsquoLogoutrsquo hyperlink in the UserMenu portlet Verify that the logout actionis successful and the UserMenu portlet disappears

525 Summary

What we have created is a portlet that is highly reusable We can easily reuse it in adifferent project with little or no modification Moreover the design of this portlet followsclosely the philosophy that logic and presentation should be separated While we did notpoint this out in the previous sections such practice is used nearly everywhere in a typicalYii application

53 Creating Tag Cloud Portlet

Tag cloud displays a list of post tags with visual decorations hinting the popularity of eachindividual tag

531 Creating TagCloud Class

We create the TagCloud class in the file wwwrootblogprotectedcomponentsTagCloud

php The file has the following content

44 5 Portlets

Yiiimport(rsquoziiwidgetsCPortletrsquo)

class TagCloud extends CPortlet

public $title=rsquoTagsrsquo

public $maxTags=20

protected function renderContent()

$tags=Tagmodel()-gtfindTagWeights($this-gtmaxTags)

foreach($tags as $tag=gt$weight)

$link=CHtmllink(CHtmlencode($tag) array(rsquopostindexrsquorsquotagrsquo=gt$tag))

echo CHtmltag(rsquospanrsquo array(

rsquoclassrsquo=gtrsquotagrsquo

rsquostylersquo=gtfont-size$weightpt) $link)n

Unlike the UserMenu portlet the TagCloud portlet does not use a view Instead its presen-tation is done in the renderContent() method This is because the presentation does notcontain much HTML tags

We display each tag as a hyperlink to the post index page with the corresponding tagparameter The font size of each tag link is adjusted according to their relative weightamong other tags If a tag has higher frequency value than the other it will have a biggerfont size

532 Using TagCloud Portlet

Usage of the TagCloud portlet is very simple We modify the layout file wwwrootblog

protectedviewslayoutsmainphp as follows

ltdiv id=sidebargt

ltphp if(Yiiapp()-gtuser-gtisGuest) $this-gtwidget(rsquoUserMenursquo) gt

ltphp $this-gtwidget(rsquoTagCloudrsquo array(

rsquomaxTagsrsquo=gtYiiapp()-gtparams[rsquotagCloudCountrsquo]

)) gt

ltdivgt

54 Creating Recent Comments Portlet 45

54 Creating Recent Comments Portlet

In this section we create the last portlet that displays a list of comments recently pub-lished

541 Creating RecentComments Class

We create the RecentComments class in the file wwwrootblogprotectedcomponentsRecentCommentsphp The file has the following content

Yiiimport(rsquoziiwidgetsCPortletrsquo)

class RecentComments extends CPortlet

public $title=rsquoRecent Commentsrsquo

public $maxComments=10

public function getRecentComments()

return Commentmodel()-gtfindRecentComments($this-gtmaxComments)

protected function renderContent()

$this-gtrender(rsquorecentCommentsrsquo)

In the above we invoke the findRecentComments method which is defined in the Comment

class as follows

class Comment extends CActiveRecord

public function findRecentComments($limit=10)

return $this-gtwith(rsquopostrsquo)-gtfindAll(array(

rsquoconditionrsquo=gtrsquotstatus=rsquoselfSTATUS APPROVED

rsquoorderrsquo=gtrsquotcreate time DESCrsquo

rsquolimitrsquo=gt$limit

))

46 5 Portlets

542 Creating recentComments View

The recentComments view is saved in the file wwwrootblogprotectedcomponentsviews

recentCommentsphp It simply displays every comment returned by the RecentComments

getRecentComments() method

543 Using RecentComments Portlet

We modify the layout file wwwrootblogprotectedviewslayoutsmainphp to embedthis last portlet

ltdiv id=sidebargt

ltphp if(Yiiapp()-gtuser-gtisGuest) $this-gtwidget(rsquoUserMenursquo) gt

ltphp $this-gtwidget(rsquoTagCloudrsquo array(

rsquomaxTagsrsquo=gtYiiapp()-gtparams[rsquotagCloudCountrsquo]

)) gt

ltphp $this-gtwidget(rsquoRecentCommentsrsquo array(

rsquomaxCommentsrsquo=gtYiiapp()-gtparams[rsquorecentCommentCountrsquo]

)) gt

ltdivgt

Chapter 6

Final Work

61 Beautifying URLs

The URLs linking various pages of our blog application currently look ugly For examplethe URL for the page showing a post looks like the following

indexphpr=postshowampid=1

In this section we describe how to beautify these URLs and make them SEO-friendlyOur goal is to be able to use the following URLs in the application

1 indexphppostsyii leads to the page showing a list of posts with tag yii

2 indexphppost2A+Test+Post leads to the page showing the detail of the postwith ID 2 whose title is A Test Post

3 indexphppostupdate1 leads to the page that allows updating the post with ID1

Note that in the second URL format we include the post title in the URL This is mainlyto make the URL SEO friendly It is said that search engines may also respect the wordsfound in a URL when it is being indexed

To achieve our goal we modify the application configuration as follows

return array(

rsquocomponentsrsquo=gtarray(

rsquourlManagerrsquo=gtarray(

rsquourlFormatrsquo=gtrsquopathrsquo

rsquorulesrsquo=gtarray(

48 6 Final Work

rsquopostltidd+gtlttitlegtrsquo=gtrsquopostviewrsquorsquopostslttaggtrsquo=gtrsquopostindexrsquo

)

)

)

)

In the above we configure the urlManager component by setting its urlFormat propertyto be path and adding a set of rules

The rules are used by urlManager to parse and create the URLs in the desired formatFor example the first rule says that if a URL indexphppostsyii is requested theurlManager component should be responsible to dispatch the request to the route post

index and generate a tag GET parameter with the value yii On the other hand whencreating a URL with the route postindex and parameter tag the urlManager componentwill also use this rule to generate the desired URL indexphppostsyii For this reasonwe say that urlManager is a two-way URL manager

The urlManager component can further beautify our URLs such as hiding indexphp inthe URLs appending suffix like html to the URLs We can obtain these features easilyby configuring various properties of urlManager in the application configuration For moredetails please refer to the Guide

62 Logging Errors

A production Web application often needs sophisticated logging for various events In ourblog application we would like to log the errors occurring when it is being used Sucherrors could be programming mistakes or usersrsquo misuse of the system Logging these errorswill help us to improve the blog application

We enable the error logging by modifying the application configuration as follows

return array(

rsquopreloadrsquo=gtarray(rsquologrsquo)

rsquocomponentsrsquo=gtarray(

rsquologrsquo=gtarray(

rsquoclassrsquo=gtrsquoCLogRouterrsquo

rsquoroutesrsquo=gtarray(

array(

rsquoclassrsquo=gtrsquoCFileLogRoutersquo

rsquolevelsrsquo=gtrsquoerror warningrsquo

63 Final Tune-up and Deployment 49

)

)

)

)

)

With the above configuration if an error or warning occurs detailed information will belogged and saved in a file located under the directory wwwrootblogprotectedruntime

The log component offers more advanced features such as sending log messages to a listof email addresses displaying log messages in JavaScript console window etc For moredetails please refer to the Guide

63 Final Tune-up and Deployment

We are close to finish our blog application Before deployment we would like to do sometune-ups

631 Changing Home Page

We change to use the post list page as the home page We modify the application config-uration as follows

return array(

rsquodefaultControllerrsquo=gtrsquopostrsquo

)

Tip Because PostController already declares list to be its default action whenwe access the home page of the application we will see the result generated by thelist action of the post controller

632 Enabling Schema Caching

Because ActiveRecord relies on the metadata about tables to determine the column infor-mation it takes time to read the metadata and analyze it This may not be a problemduring development stage but for an application running in production mode it is a totalwaste of time if the database schema does not change Therefore we should enable theschema caching by modifying the application configuration as follows

50 6 Final Work

return array(

rsquocomponentsrsquo=gtarray(

rsquocachersquo=gtarray(

rsquoclassrsquo=gtrsquoCDbCachersquo

)

rsquodbrsquo=gtarray(

rsquoclassrsquo=gtrsquosystemdbCDbConnectionrsquo

rsquoconnectionStringrsquo=gtrsquosqlitewwwrootblogprotecteddatablogdbrsquo

rsquoschemaCachingDurationrsquo=gt3600

)

)

)

In the above we first add a cache component which uses a default SQLite database as thecaching storage If our server is equipped with other caching extensions such as APCwe could change to use them as well We also modify the db component by setting itsschemaCachingDuration property to be 3600 which means the parsed database schemadata can remain valid in cache for 3600 seconds

633 Disabling Debugging Mode

We modify the entry script file wwwrootblogindexphp by removing the line definingthe constant YII DEBUG This constant is useful during development stage because it allowsYii to display more debugging information when an error occurs However when theapplication is running in production mode displaying debugging information is not agood idea because it may contain sensitive information such as where the script file islocated and the content in the file etc

634 Deploying the Application

The final deployment process manly involves copying the directory wwwrootblog to thetarget directory The following checklist shows every needed step

1 Install Yii in the target place if it is not available

2 Copy the entire directory wwwrootblog to the target place

3 Edit the entry script file indexphp by pointing the $yii variable to the new Yiibootstrap file

4 Edit the file protectedyiicphp by setting the $yiic variable to be the new Yiiyiicphp file

64 Future Enhancements 51

5 Change the permission of the directories assets and protectedruntime so that theyare writable by the Web server process

64 Future Enhancements

641 Using a Theme

Without writing any code our blog application is already themeable To use a themewe mainly need to develop the theme by writing customized view files in the theme Forexample to use a theme named classic that uses a different page layout we would createa layout view file wwwrootblogthemesclassicviewslayoutsmainphp We also needto change the application configuration to indicate our choice of the classic theme

return array(

rsquothemersquo=gtrsquoclassicrsquo

)

642 Internationalization

We may also internationalize our blog application so that its pages can be displayed indifferent languages This mainly involves efforts in two aspects

First we may create view files in different languages For example for the list pageof PostController we can create a view file wwwrootblogprotectedviewspostzh cn

listphp When the application is configured to use simplified Chinese (the language codeis zh cn) Yii will automatically use this new view file instead of the original one

Second we may create message translations for those messages generated by code Themessage translations should be saved as files under the directory wwwrootblogprotected

messages We also need to modify the code where we use text strings by enclosing themin the method call Yiit()

For more details about internationalization please refer to the Guide

643 Improving Performance with Cache

While the Yii framework itself is very efficient it is not necessarily true that an applicationwritten in Yii efficient There are several places in our blog application that we can improvethe performance For example the tag clould portlet could be one of the performancebottlenecks because it involves complex database query and PHP logic

52 6 Final Work

We can make use of the sophisticated caching feature provided by Yii to improve theperformance One of the most useful components in Yii is COutputCache which caches afragment of page display so that the underlying code generating the fragment does not needto be executed for every request For example in the layout file wwwrootblogprotected

viewslayoutsmainphp we can enclose the tag cloud portlet with COutputCache

ltphp if($this-gtbeginCache(rsquotagCloudrsquo array(rsquodurationrsquo=gt3600))) gt

ltphp $this-gtwidget(rsquoTagCloudrsquo array(

rsquomaxTagsrsquo=gtYiiapp()-gtparams[rsquotagCloudCountrsquo]

)) gt

ltphp $this-gtendCache() gt

With the above code the tag cloud display will be served from cache instead of beinggenerated on-the-fly for every request The cached content will remain valid in cache for3600 seconds

644 Adding New Features

Our blog application only has very basic functionalities To become a complete blogsystem more features are needed for example calendar portlet email notifications postcategorization archived post portlet and so on We will leave the implementation of thesefeatures to interested readers

  • Contents
  • License
  • Getting Started
    • Building a Blog System using Yii
    • Testdriving with Yii
      • Installing Yii
      • Creating Skeleton Application
      • Application Workflow
        • Requirements Analysis
        • Overall Design
          • Initial Prototyping
            • Setting Up Database
              • Creating Database
              • Establishing Database Connection
                • Scaffolding
                • Authenticating User
                • Summary
                  • Post Management
                    • Customizing Post Model
                      • Customizing rules() Method
                      • Customizing relations() Method
                      • Representing Status in Text
                        • Creating and Updating Posts
                          • Customizing Access Control
                          • Customizing create and update Operations
                            • Displaying Posts
                              • Customizing view Operation
                              • Customizing index Operation
                                • Managing Posts
                                  • Listing Posts in Tabular View
                                  • Deleting Posts
                                      • Comment Management
                                        • Customizing Comment Model
                                          • Customizing rules() Method
                                          • Customizing attributeLabels() Method
                                          • Customizing Saving Process
                                            • Creating and Displaying Comments
                                              • Displaying Comments
                                              • Creating Comments
                                                • Managing Comments
                                                  • Updating and Deleting Comments
                                                  • Approving Comments
                                                      • Portlets
                                                        • Customizing Page Layout
                                                        • Creating User Menu Portlet
                                                          • Creating UserMenu Class
                                                          • Creating userMenu View
                                                          • Using UserMenu Portlet
                                                          • Testing UserMenu Portlet
                                                          • Summary
                                                            • Creating Tag Cloud Portlet
                                                              • Creating TagCloud Class
                                                              • Using TagCloud Portlet
                                                                • Creating Recent Comments Portlet
                                                                  • Creating RecentComments Class
                                                                  • Creating recentComments View
                                                                  • Using RecentComments Portlet
                                                                      • Final Work
                                                                        • Beautifying URLs
                                                                        • Logging Errors
                                                                        • Final Tune-up and Deployment
                                                                          • Changing Home Page
                                                                          • Enabling Schema Caching
                                                                          • Disabling Debugging Mode
                                                                          • Deploying the Application
                                                                            • Future Enhancements
                                                                              • Using a Theme
                                                                              • Internationalization
                                                                              • Improving Performance with Cache
                                                                              • Adding New Features
Page 18: Yii Blog 1.1.0 Demo Tutor

22 Scaffolding 11

Tip If you want to use MySQL instead of SQLite to store data you may cre-ate a MySQL database named blog using the SQL statements in wwwrootyii

demosblogprotecteddataschemamysqlsql Then modify the applicationconfiguration as follows

return array(

rsquocomponentsrsquo=gtarray(

rsquodbrsquo=gtarray(

rsquoconnectionStringrsquo =gt rsquomysqlhost=localhostdbname=blogrsquo

rsquoemulatePreparersquo =gt true

rsquousernamersquo =gt rsquorootrsquo

rsquopasswordrsquo =gt rsquorsquo

rsquocharsetrsquo =gt rsquoutf8rsquo

rsquotablePrefixrsquo =gt rsquotbl rsquo

)

)

)

22 Scaffolding

Create read update and delete (CRUD) are the four basic operations of data objects in anapplication Because the task of implementing the CRUD operations is so common whendeveloping Web applications Yii provides a tool to automate this process (also known asscaffolding) In this section we will describe how to use the tool to implement CRUD forposts and comments

Open a command window and run the following commands

wwwrootyiiframeworkyiic shell wwwrootblogindexphp

Yii Interactive Tool v11

Please type rsquohelprsquo for help Type rsquoexitrsquo to quit

gtgt model

gtgt crud Post

gtgt crud Comment

gtgt exit

12 2 Initial Prototyping

Info Some PHP installations may use a different phpini file for command line(CLI) PHP parser As a result when running the above yiic commands youmay encounter errors like rdquoYiiBaseinclude(PDOphp) failed to open streamrdquoor rdquocould not find driverrdquo Please double check your CLI PHP configuration byexecuting the following command

php -r phpinfo()

The result of the above command will show which phpini file is being used andwhich extensions are loaded If a wrong phpini file is used you may use thefollowing command to explicitly specify the correct phpini to use

php -c phpini wwwrootyiiframeworkyiicphp shell wwwrootblogindexphp

The commands above accomplish two tasks First the model command generates a modelclass file for each table in the blog database Second the crud commands generate thecode needed by the CRUD operations for the Post and Comment models

Tip The command model generates a model class for every table in the databaseSometimes we probably donrsquot want to do so (eg the database contains some irrel-evant tables) In this case we can create model classes one by one For exampleto create the User model we can use the command model User The model com-mand also has some more advanced usages For more details execute help model

command

We can test the generated code by accessing the following URLs

httpwwwexamplecomblogindexphpr=post

httpwwwexamplecomblogindexphpr=comment

Notice that the post and comment features implemented by the generated code are com-pletely independent of each other Also when creating a new post or comment we arerequired to enter information such as author id and create time which in real applica-tion should be set by the program Donrsquot worry We will fix these problems in the nextmilestones For now we should be fairly satisfied as this prototype already contains mostfeatures that we need to implement for the blog application

To prepare for the next milestones letrsquos take a closer look at the files generated by theabove commands All the files are generated under wwwrootblogprotected For conve-nience we group them into model files controller files and view files

22 Scaffolding 13

bull model files

ndash modelsUserphp contains the User class that extends from CActiveRecord andcan be used to access the tbl user database table

ndash modelsPostphp contains the Post class that extends from CActiveRecord andcan be used to access the tbl post database table

ndash modelsTagphp contains the Tag class that extends from CActiveRecord andcan be used to access the tbl tag database table

ndash modelsCommentphp contains the Comment class that extends from CActiveRe-cord and can be used to access the tbl comment database table

ndash modelsLookupphp contains the Lookup class that extends from CActiveRecordand can be used to access the tbl lookup database table

bull controller file

ndash controllersPostControllerphp contains the PostController class which is thecontroller in charge of all CRUD operations about posts

ndash controllersCommentControllerphp contains the CommentController class whichis the controller in charge of all CRUD operations about comments

bull view files

ndash viewspostcreatephp is the view file that shows an HTML form to create anew post

ndash viewspostupdatephp is the view file that shows an HTML form to update anexisting post

ndash viewspostviewphp is the view file that displays the detailed information of apost

ndash viewspostindexphp is the view file that displays a list of posts

ndash viewspostadminphp is the view file that displays posts in a table with ad-ministrative commands

ndash viewspost formphp is the partial view file embedded in viewspostcreate

php and viewspostupdatephp It displays the HTML form for collecting postinformation

ndash viewspost viewphp is the partial view file used by viewspostindexphp Itdisplays the brief view of a single post

ndash a similar set of view files are also generated for comment

14 2 Initial Prototyping

In order to understand better how the above files are used we show in the following theworkflow that occurs in the blog application when displaying a list of posts

1 The user requests the URL httpwwwexamplecomblogindexphpr=posts

2 The entry script is executed by the Web server which creates and initializes anapplication instance to handle the request

3 The application creates an instance of PostController and executes it

4 The PostController instance executes the index action by calling its actionIndex()

method Note that index is the default action if the user does not specify an actionto execute in the URL

5 The actionIndex() method queries database to bring back the list of recent posts

6 The actionIndex() method renders the index view with the post data

23 Authenticating User

Our blog application needs to differentiate between the system owner and guest usersTherefore we need to implement the user authentication feature

As you may have found that the skeleton application already provides user authenticationby checking if the username and password are both demo or admin In this section wewill modify the corresponding code so that the authentication is done against the User

database table

User authentication is performed in a class implementing the IUserIdentity interface Theskeleton application uses the UserIdentity class for this purpose The class is stored inthe file wwwrootblogprotectedcomponentsUserIdentityphp

Tip By convention the name of a class file must be the same as the correspondingclass name suffixed with the extension php Following this convention one canrefer to a class using a path alias For example we can refer to the UserIdentity

class with the alias applicationcomponentsUserIdentity Many APIs in Yiican recognize path aliases (eg YiicreateComponent()) and using path aliasesavoids the necessity of embedding absolute file paths in the code The existence ofthe latter often causes trouble when we deploy an application

We modify the UserIdentity class as follows

23 Authenticating User 15

ltphp

class UserIdentity extends CUserIdentity

private $ id

public function authenticate()

$username=strtolower($this-gtusername)

$user=Usermodel()-gtfind(rsquoLOWER(username)=rsquoarray($username))

if($user===null)

$this-gterrorCode=selfERROR USERNAME INVALID

else if($user-gtvalidatePassword($this-gtpassword))

$this-gterrorCode=selfERROR PASSWORD INVALID

else

$this-gt id=$user-gtid

$this-gtusername=$user-gtusername

$this-gterrorCode=selfERROR NONE

return $this-gterrorCode==selfERROR NONE

public function getId()

return $this-gt id

In the authenticate() method we use the User class to look for a row in the User tablewhose username column is the same as the given username in a case-insensitive mannerRemember that the User class was created using the yiic tool in the prior section Becausethe User class extends from CActiveRecord we can exploit the ActiveRecord feature toaccess the User table in an OOP fashion

In order to check if the user has entered a valid password we invoke the validatePassword

method of the User class We need to modify the file wwwrootblogprotectedmodels

Userphp as follows Note that instead of storing the plain password in the database westore the hash result of the password and a randomly generated salt key When validatingthe user-entered password we should compare the hash results instead

class User extends CActiveRecord

public function validatePassword($password)

return $this-gthashPassword($password$this-gtsalt)===$this-gtpassword

16 2 Initial Prototyping

public function hashPassword($password$salt)

return md5($salt$password)

In the UserIdentity class we also override the getId() method which returns the id valueof the user found in the User table The parent implementation would return the usernameinstead Both the username and id properties will be stored in the user session and maybe accessed via Yiiapp()-gtuser from anywhere in our code

Tip In the UserIdentity class we reference the class CUserIdentity without ex-plicitly including the corresponding class file This is because CUserIdentity is acore class provided by the Yii framework Yii will automatically include the classfile for any core class when it is referenced for the first time

We also do the same with the User class This is because the User class file is placedunder the directory wwwrootblogprotectedmodels which has been added tothe PHP include path according to the following lines found in the applicationconfiguration

return array(

rsquoimportrsquo=gtarray(

rsquoapplicationmodelsrsquo

rsquoapplicationcomponentsrsquo

)

)

The above configuration says that any class whose class file is located under either wwwrootblogprotectedmodels or wwwrootblogprotectedcomponents willbe automatically included when the class is referenced for the first time

The UserIdentity class is mainly used by the LoginForm class to authenticate a user basedon the username and password input collected from the login page The following codefragment shows how UserIdentity is used

$identity=new UserIdentity($username$password)

$identity-gtauthenticate()

switch($identity-gterrorCode)

24 Summary 17

case UserIdentityERROR NONE

Yiiapp()-gtuser-gtlogin($identity)

break

Info People often get confused about identity and the user application componentThe former represents a way of performing authentication while the latter is usedto represent the information related with the current user An application can onlyhave one user component but it can have one or several identity classes dependingon what kind of authentication it supports Once authenticated an identity instancemay pass its state information to the user component so that they are globallyaccessible via user

To test the modified UserIdentity class we can browse the URL httpwwwexample

comblogindexphp and try logging in with the username and password that we store inthe User table If we use the database provided by the blog demo we should be able tologin with username demo and password demo Note that this blog system does not providethe user management feature As a result a user cannot change his account or create anew one through the Web interface The user management feature may be considered asa future enhancement to the blog application

24 Summary

We have completed the milestone 1 Letrsquos summarize what we have done so far

1 We identified the requirements to be fulfilled

2 We installed the Yii framework

3 We created a skeleton application

4 We designed and created the blog database

5 We modified the application configuration by adding the database connection

6 We generated the code that implements the basic CRUD operations for both postsand comments

7 We modified the authentication method to check against the tbl user table

18 2 Initial Prototyping

For a new project most of the time will be spent in step 1 and 4 for this first milestone

Although the code generated by the yiic tool implements fully functional CRUD oper-ations for a database table it often needs to be modified in practical applications Forthis reason in the next two milestone our job is to customize the generated CRUD codeabout posts and comments so that it reaches our initial requirements

In general we first modify the model class file by adding appropriate validation rules anddeclaring relational objects We then modify the controller action and view code for eachindividual CRUD operation

Chapter 3

Post Management

31 Customizing Post Model

The Post model class generated by the yiic tool mainly needs to be modified in two places

bull the rules() method specifies the validation rules for the model attributes

bull the relations() method specifies the related objects

Info A model consists of a list of attributes each associated with a column in thecorresponding database table Attributes can be declared explicitly as class membervariables or implicitly without any declaration

311 Customizing rules() Method

We first specify the validation rules which ensure the attribute values entered by usersare correct before they are saved to the database For example the status attribute ofPost should be an integer 1 2 or 3 The yiic tool also generates validation rules for eachmodel However these rules are based on the table column information and may not beappropriate

Based on the requirement analysis we modify the rules() method as follows

public function rules()

return array(

array(rsquotitle content statusrsquo rsquorequiredrsquo)

array(rsquotitlersquo rsquolengthrsquo rsquomaxrsquo=gt128)

array(rsquostatusrsquo rsquoinrsquo rsquorangersquo=gtarray(123))

array(rsquotagsrsquo rsquomatchrsquo rsquopatternrsquo=gtrsquo^[ws]+$rsquorsquomessagersquo=gtrsquoTags can only contain word charactersrsquo)

20 3 Post Management

array(rsquotagsrsquo rsquonormalizeTagsrsquo)

)

In the above we specify that the title content and status attributes are required thelength of title should not exceed 128 the status attribute value should be 1 (draft) 2(published) or 3 (archived) and the tags attribute should only contain word charactersand commas In addition we use normalizeTags to normalize the user-entered tags so thatthe tags are unique and properly separated with commas

The validators such as required length in and match are all built-in validators providedby Yii The normalizeTags validator is a method-based validator that we need to definein the Post class For more information about how to specify validation rules please referto the Guide

public function normalizeTags($attribute$params)

$this-gttags=Tagarray2string(array unique(Tagstring2array($this-gttags)))

The rules declared in this method are executed one by one when we call the validate() orsave() method of the model instance

Note It is very important to remember that attributes appearing in rules() mustbe those to be entered by end users Other attributes such as id and create time

in the Post model which are set by our code or database should not be in rules()For more details please refer to Securing Attribute Assignments

After making these changes we can visit the post creation page again to verify that thenew validation rules are taking effect

312 Customizing relations() Method

Lastly we customize the relations() method to specify the related objects of a post Bydeclaring these related objects in relations() we can exploit the powerful RelationalActiveRecord (RAR) feature to access the related object information of a post such asits author and comments without the need to write complex SQL JOIN statements

We customize the relations() method as follows

31 Customizing Post Model 21

public function relations()

return array(

rsquoauthorrsquo =gt array(selfBELONGS TO rsquoUserrsquo rsquoauthor idrsquo)

rsquocommentsrsquo =gt array(selfHAS MANY rsquoCommentrsquo rsquopost idrsquo

rsquoconditionrsquo=gtrsquocommentsstatus=rsquoCommentSTATUS APPROVED

rsquoorderrsquo=gtrsquocommentscreate time DESCrsquo)

rsquocommentCountrsquo =gt array(selfSTAT rsquoCommentrsquo rsquopost idrsquo

rsquoconditionrsquo=gtrsquostatus=rsquoCommentSTATUS APPROVED)

)

The above relations state that

bull A post belongs to an author whose class is User and the relationship is establishedbased on the author id attribute value of the post

bull A post has many comments whose class is Comment and the relationship is establishedbased on the post id attribute value of the comments These comments should besorted according to their creation time and the comments must be approved

bull The commentCount relation is a bit special as it returns back an aggregation resultwhich is about how many comments the post has

With the above relation declaration we can easily access the author and comments of apost like the following

$author=$post-gtauthor

echo $author-gtusername

$comments=$post-gtcomments

foreach($comments as $comment)

echo $comment-gtcontent

For more details about how to declare and use relations please refer to the Guide

313 Representing Status in Text

Because the status of a post is stored as an integer in the database we need to provide atextual representation so that it is more intuitive when being displayed to end users In alarge system the similar requirement is very common

22 3 Post Management

As a generic solution we use the tbl lookup table to store the mapping between integervalues and textual representations that are needed by other data objects We modify theLookup model class as follows to more easily access the textual data in the table

class Lookup extends CActiveRecord

private static $ items=array()

public static function items($type)

if(isset(self$ items[$type]))

selfloadItems($type)

return self$ items[$type]

public static function item($type$code)

if(isset(self$ items[$type]))

selfloadItems($type)

return isset(self$ items[$type][$code]) self$ items[$type][$code] false

private static function loadItems($type)

self$ items[$type]=array()

$models=selfmodel()-gtfindAll(array(

rsquoconditionrsquo=gtrsquotype=typersquo

rsquoparamsrsquo=gtarray(rsquotypersquo=gt$type)

rsquoorderrsquo=gtrsquopositionrsquo

))

foreach($models as $model)

self$ items[$type][$model-gtcode]=$model-gtname

Our new code mainly provides two static methods Lookupitems() and Lookupitem()The former returns a list of strings belonging to the specified data type while the latterreturns a particular string for the given data type and data value

Our blog database is pre-populated with two lookup types PostStatus and CommentStatusThe former refers to the possible post statuses while the latter the comment statuses

In order to make our code easier to read we also declare a set of constants to representthe status integer values We should use these constants through our code when referringto the corresponding status values

32 Creating and Updating Posts 23

class Post extends CActiveRecord

const STATUS DRAFT=0

const STATUS PUBLISHED=1

const STATUS ARCHIVED=2

Therefore we can call Lookupitems(rsquoPostStatusrsquo) to get the list of possible post statuses(text strings indexed by the corresponding integer values) and call Lookupitem(rsquoPostStatusrsquoPostSTATUS PUBLISHED) to get the string representation of the published status

32 Creating and Updating Posts

With the Post model ready we need to fine-tune the actions and views for the controllerPostController In this section we first customize the access control of CRUD operationswe then modify the code implementing the create and update operations

321 Customizing Access Control

The first thing we want to do is to customize the access control because the code generatedby yiic does not fit our needs

We modify the accessRules() method in the file wwwrootblogprotectedcontrollers

PostControllerphp as follows

public function accessRules()

return array(

array(rsquoallowrsquo allow all users to perform rsquolistrsquo and rsquoshowrsquo actions

rsquoactionsrsquo=gtarray(rsquoindexrsquo rsquoviewrsquo)

rsquousersrsquo=gtarray(rsquorsquo)

)

array(rsquoallowrsquo allow authenticated users to perform any action

rsquousersrsquo=gtarray(rsquorsquo)

)

array(rsquodenyrsquo deny all users

rsquousersrsquo=gtarray(rsquorsquo)

)

)

The above rules state that all users can access the index and view actions and authenti-cated users can access any actions including the admin action The user should be denied

24 3 Post Management

access in any other scenario Note that these rules are evaluated in the order they arelisted here The first rule matching the current context makes the access decision Forexample if the current user is the system owner who tries to visit the post creation pagethe second rule will match and it will give the access to the user

322 Customizing create and update Operations

The create and update operations are very similar They both need to display an HTMLform to collect user inputs validate them and save them into database The main differ-ence is that the update operation will pre-populate the form with the existing post datafound in the database For this reason the yiic tool generates a partial view wwwroot

blogprotectedviewspost formphp that is embedded in both the create and update

views to render the needed HTML form

We first change the formphp file so that the HTML form only collects the inputs wewant title content tags and status We use plain text fields to collect inputs for thefirst three attributes and a dropdown list to collect input for status The dropdown listoptions are the text displays of the possible post statuses

ltphp echo CHtmlactiveDropDownList($postrsquostatusrsquoLookupitems(rsquoPostStatusrsquo)) gt

In the above we call Lookupitems(rsquoPostStatusrsquo) to bring back the list of post statuses

We then modify the Post class so that it can automatically set some attributes (eg create

time author id) before a post is saved to the database We override the beforeSave()

method as follows

protected function beforeSave()

if(parentbeforeSave())

if($this-gtisNewRecord)

$this-gtcreate time=$this-gtupdate time=time()

$this-gtauthor id=Yiiapp()-gtuser-gtid

else

$this-gtupdate time=time()

return true

else

return false

33 Displaying Posts 25

When we save a post we want to update the tbl tag table to reflect the change of tagfrequencies We can do this work in the afterSave() method which is automaticallyinvoked by Yii after a post is successfully saved into the database

protected function afterSave()

parentafterSave()

Tagmodel()-gtupdateFrequency($this-gt oldTags $this-gttags)

private $ oldTags

protected function afterFind()

parentafterFind()

$this-gt oldTags=$this-gttags

In the implementation because we want to detect if the user changes the tags in case he isupdating an existing post we need to know what the old tags are For this reason we alsowrite the afterFind() method to keep the old tags in the variable oldTags The methodafterFind() is invoked automatically by Yii when an AR record is populated with thedata from database

We are not going to give details of the TagupdateFrequency() method here Interestedreaders may refer to the file wwwrootyiidemosblogprotectedmodelsTagphp

33 Displaying Posts

In our blog application a post may be displayed among a list of posts or by itself Theformer is implemented as the index operation while the latter the view operation In thissection we customize both operations to fulfill our initial requirements

331 Customizing view Operation

The view operation is implemented by the actionView() method in PostController Itsdisplay is generated by the view view with the view file wwwrootblogprotectedviews

postviewphp

Below is the relevant code implementing the view operation in PostController

public function actionView()

26 3 Post Management

$this-gtrender(rsquoviewrsquoarray(

$post=$this-gtloadModel()

$this-gtrender(rsquoviewrsquoarray(

rsquomodelrsquo=gt$post

))

))

private $ model

public function loadModel()

if($this-gt model===null)

if(isset($ GET[rsquoidrsquo]))

if(Yiiapp()-gtuser-gtisGuest)

$condition=rsquostatus=rsquoPostSTATUS PUBLISHED

rsquo OR status=rsquoPOSTSTATUS ARCHIVED

else

$condition=rsquorsquo

$this-gt model=Postmodel()-gtfindbyPk($ GET[rsquoidrsquo] $condition)

if($this-gt model===null)

throw new CHttpException(404rsquoThe requested page does not existrsquo)

return $this-gt model

Our change mainly lies in the loadModel() method In this method we query the Post

table according to the id GET parameter If the post is not found or if it is not publishedor archived (when the user is a guest) we will throw a 404 HTTP error Otherwise thepost object is returned to actionView() which in turn passes the post object to the viewscript for further display

Tip Yii captures HTTP exceptions (instances of CHttpException) and displaysthem in either predefined templates or customized error views The skeleton appli-cation generated by yiic already contains a customized error view in wwwroot

blogprotectedviewssiteerrorphp We can modify this file if we want tofurther customize the error display

The change in the view script is mainly about ajdusting the formatting and styles of thepost display We will not go into details here Interested readers may refer to wwwroot

blogprotectedviewspostviewphp

33 Displaying Posts 27

332 Customizing index Operation

Like the view operation we customize the index operation in two places the actionIndex()

method in PostController and the view file wwwrootblogprotectedviewspostindex

php We mainly need to add the support for displaying a list of posts that are associatedwith a specified tag

Below is the modified actionIndex() method in PostController

public function actionIndex()

$criteria=new CDbCriteria(array(

rsquoconditionrsquo=gtrsquostatus=rsquoPostSTATUS PUBLISHED

rsquoorderrsquo=gtrsquoupdate time DESCrsquo

rsquowithrsquo=gtrsquocommentCountrsquo

))

if(isset($ GET[rsquotagrsquo]))

$criteria-gtaddSearchCondition(rsquotagsrsquo$ GET[rsquotagrsquo])

$dataProvider=new CActiveDataProvider(rsquoPostrsquo array(

rsquopaginationrsquo=gtarray(

rsquopageSizersquo=gtYiiapp()-gtparams[rsquopostsPerPagersquo]

)

rsquocriteriarsquo=gt$criteria

))

$this-gtrender(rsquoindexrsquoarray(

rsquodataProviderrsquo=gt$dataProvider

))

In the above we first create a query criteria for retrieving post list The criteria statesthat only published posts should be returned and they should be sorted according to theirupdate time in descending order Because when displaying a post in the list we want toshow how many comments the post has received in the criteria we also specify to bringback commentCount which if you remember is a relation declared in Postrelations()

In case when a user wants to see posts with a specific tag we would add a search conditionto the criteria to look for the specified tag

Using the query criteria we create a data provider which mainly serves for three purposesFirst it does pagination of the data when too many results may be returned Herewe customize the pagination by setting the page size to be an application parameterpostsPerPage Second it does sorting according to the user request And finally it feedsthe paginated and sorted data to widgets or view code for presentation

28 3 Post Management

After we finish with actionIndex() we modify the index view as follows Our change ismainly about adding the h1 header when the user specifies to display posts with a tag

ltphp if(empty($ GET[rsquotagrsquo])) gt

lth1gtPosts Tagged with ltigtltphp echo CHtmlencode($ GET[rsquotagrsquo]) gtltigtlth1gt

ltphp endif gt

ltphp $this-gtwidget(rsquoziiwidgetsCListViewrsquo array(

rsquodataProviderrsquo=gt$dataProvider

rsquoitemViewrsquo=gtrsquo viewrsquo

rsquotemplatersquo=gtitemsnpager)) gt

Note that in the above we use [CListView] to display the post list This widget requires apartial view to display the detail of each individual post Here we specify the partial viewto be view which means the file wwwrootblogprotectedviewspost viewphp In thisview script we can acccess the post instance being displayed via a local variable named$data

34 Managing Posts

Managing posts mainly refers to listing posts in an administrative view that allows us tosee posts with all statuses updating them and deleting them They are accomplished bythe admin operation and the delete operation respectively The code generated by yiic

does not need much modification Below we mainly explain how these two operations areimplemented

341 Listing Posts in Tabular View

The admin operation shows posts with all statuses in a tabular view The view supportssorting and pagination The following is the actionAdmin() method in PostController

public function actionAdmin()

$dataProvider=new CActiveDataProvider(rsquoPostrsquo array(

rsquosortrsquo=gtarray(

rsquodefaultOrderrsquo=gtrsquostatus update time DESCrsquo

)

))

$this-gtrender(rsquoadminrsquoarray(

rsquodataProviderrsquo=gt$dataProvider

))

34 Managing Posts 29

The above code is very similar to that in actionIndex() The main difference is in thequery criteria Here we do not specify any particular query criteria since we want to seeall posts We do customize the sort property of the data provider so that by default theposts are shown in the order of their statuses and update time

Below is the code for the admin view

lth1gtManage Postslth1gt

ltphp $this-gtwidget(rsquoziiwidgetsgridCGridViewrsquo array(

rsquodataProviderrsquo=gt$dataProvider

rsquocolumnsrsquo=gtarray(

array(

rsquonamersquo=gtrsquotitlersquo

rsquotypersquo=gtrsquorawrsquo

rsquovaluersquo=gtrsquoCHtmllink(CHtmlencode($data-gttitle) $data-gturl)rsquo

)

array(

rsquonamersquo=gtrsquostatusrsquo

rsquovaluersquo=gtrsquoLookupitem(PostStatus$data-gtstatus)rsquo

)

rsquocreate timedatetimersquo

rsquoupdate timedatetimersquo

array(

rsquoclassrsquo=gtrsquoCButtonColumnrsquo

)

)

)) gt

We use [CGridView] to display the posts It allows us to sort by a column and paginatethrough the posts if there are too many to be displayed in a single page Our change ismainly about how to display each column For example for the title column we specifythat it should be displayed as a hyperlink that points to the detailed view of the post

Tip When displaying text we call CHtmlencode() to encode HTML entities init This prevents from cross-site scripting attack

342 Deleting Posts

In the admin data grid there is a delete button in each row Clicking on the button shoulddelete the corresponding post Internally this triggers the delete action implemented asfollows

30 3 Post Management

public function actionDelete()

if(Yiiapp()-gtrequest-gtisPostRequest)

we only allow deletion via POST request

$this-gtloadModel()-gtdelete()

if(isset($ POST[rsquoajaxrsquo]))

$this-gtredirect(array(rsquoindexrsquo))

else

throw new CHttpException(400rsquoInvalid request Please do not repeat this request againrsquo)

The above code is the one generated by the yiic tool without any change We wouldlike to explain a little bit more about the checking on $ POST[rsquoajaxrsquo] The [CGridView]widget has a very nice feature that its sorting pagination and deletion operations are alldone in AJAX mode by default That means the whole page does not get reloaded if anyof the above operations is performed However it is also possible that the widget runs innon-AJAX mode (by setting its ajaxUpdate property to be false or disabling JavaScript onthe client side) It is necessary for the delete action to differentiate these two scenarios ifthe delete request is made via AJAX we should not redirect the user browser otherwisewe should

Deleting a post should also cause the deletion of all comments for that post In additionwe should also update the tbl tag table regarding the tags for the deleted post Both ofthese tasks can be achieved by writing an afterDelete method in the Post model class asfollows

protected function afterDelete()

parentafterDelete()

Commentmodel()-gtdeleteAll(rsquopost id=rsquo$this-gtid)

Tagmodel()-gtupdateFrequency($this-gttags rsquorsquo)

The above code is very straightforward it first deletes all those comments whose post id

is the same as the ID of the deleted post it then updates the tbl tag table for the tags

of the deleted post

34 Managing Posts 31

Tip We have to explicitly delete all comments for the deleted post here becauseSQLite does not really support foreign key constraints In a DBMS that supportsthis constraint (such as MySQL PostgreSQL) the foreign key constraint can be setup such that the DBMS automatically deletes the related comments if the post isdeleted In that case we no longer this explicit deletion call in our code

32 3 Post Management

Chapter 4

Comment Management

41 Customizing Comment Model

For the Comment model we mainly need to customize the rules() and attributeLabels()

methods The attributeLabels() method returns a mapping between attribute namesand attribute labels We do not need to touch relations() since the code generated bythe yiic tool is good enough

411 Customizing rules() Method

We first customize the validation rules generated by the yiic tool The following rules areused for comments

public function rules()

return array(

array(rsquocontent author emailrsquo rsquorequiredrsquo)

array(rsquoauthor email urlrsquo rsquolengthrsquo rsquomaxrsquo=gt128)

array(rsquoemailrsquorsquoemailrsquo)

array(rsquourlrsquorsquourlrsquo)

)

In the above we specify that the author email and content attributes are required thelength of author email and url cannot exceed 128 the email attribute must be a validemail address and the url attribute must be a valid URL

412 Customizing attributeLabels() Method

We then customize the attributeLabels() method to declare the label display for eachmodel attribute This method returns an array consisting of name-label pairs When wecall CHtmlactiveLabel() to display an attribute label

34 4 Comment Management

public function attributeLabels()

return array(

rsquoidrsquo =gt rsquoIdrsquo

rsquocontentrsquo =gt rsquoCommentrsquo

rsquostatusrsquo =gt rsquoStatusrsquo

rsquocreate timersquo =gt rsquoCreate Timersquo

rsquoauthorrsquo =gt rsquoNamersquo

rsquoemailrsquo =gt rsquoEmailrsquo

rsquourlrsquo =gt rsquoWebsitersquo

rsquopost idrsquo =gt rsquoPostrsquo

)

Tip If the label for an attribute is not declared in attributeLabels() an algo-rithm will be used to generate an appropriate label For example a label VerifyCode will be generated for attributes verify code or verifyCode

413 Customizing Saving Process

Because we want to record the creation time of a comment we override the beforeSave()

method of Comment like we do for the Post model

protected function beforeSave()

if(parentbeforeSave())

if($this-gtisNewRecord)

$this-gtcreate time=time()

return true

else

return false

42 Creating and Displaying Comments

In this section we implement the comment display and creation features

421 Displaying Comments

Instead of displaying and creating comments on individual pages we use the post detailpage (generated by the view action of PostController) Below the post content display

42 Creating and Displaying Comments 35

we display first a list of comments belonging to that post and then a comment creationform

In order to display comments on the post detail page we modify the view script wwwrootblogprotectedviewspostviewphp as follows

ltdiv id=commentsgt

ltphp if($model-gtcommentCountgt=1) gt

lth3gt

ltphp echo $model-gtcommentCount rsquocomment(s)rsquo gt

lth3gt

ltphp $this-gtrenderPartial(rsquo commentsrsquoarray(

rsquopostrsquo=gt$model

rsquocommentsrsquo=gt$model-gtcomments

)) gt

ltphp endif gt

ltdivgt

In the above we call renderPartial() to render a partial view named comments to displaythe list of comments belonging to the current post Note that in the view we use theexpression $model-gtcomments to retrieve the comments for the post This is valid becausewe have declared a comments relation in the Post class Evaluating this expression wouldtrigger an implicit JOIN database query to bring back the proper comments This featureis known as lazy relational query

The partial view comments is not very interesting It mainly goes through every commentand displays the detail of it Interested readers may refer to wwwrootyiidemosblog

protectedpost commentsphp

422 Creating Comments

To handle comment creation we first modify the actionView() method of PostController

as follows

public function actionView()

$post=$this-gtloadModel()

$comment=$this-gtnewComment($post)

$this-gtrender(rsquoviewrsquoarray(

rsquomodelrsquo=gt$post

rsquocommentrsquo=gt$comment

))

36 4 Comment Management

protected function newComment($post)

$comment=new Comment

if(isset($ POST[rsquoCommentrsquo]))

$comment-gtattributes=$ POST[rsquoCommentrsquo]

if($post-gtaddComment($comment))

if($comment-gtstatus==CommentSTATUS PENDING)

Yiiapp()-gtuser-gtsetFlash(rsquocommentSubmittedrsquorsquoThank yoursquo)

$this-gtrefresh()

return $comment

In the above we call the newComment() method before we render view In the newComment()

method we generate a Comment instance and check if the comment form is submitted If sowe try to add the comment for the post by calling $post-gtaddComment($comment) If it goesthrough we refresh the post detail page In case the comment needs to be approved we willshow a flash message to indicate this decision A flash message is usually a confirmationmessage displayed to end users If the user clicks on the refresh button of his browser themessage will disappear

We also need to modify wwwrootblogprotectedviewspostviewphp furthermore

ltdiv id=commentsgt

lth3gtLeave a Commentlth3gt

ltphp if(Yiiapp()-gtuser-gthasFlash(rsquocommentSubmittedrsquo)) gt

ltdiv class=successgt

ltphp echo Yiiapp()-gtuser-gtgetFlash(rsquocommentSubmittedrsquo) gt

ltdivgt

ltphp else gt

ltphp $this-gtrenderPartial(rsquocomment formrsquoarray(

rsquomodelrsquo=gt$comment

)) gt

ltphp endif gt

ltdivgtlt-- comments --gt

43 Managing Comments 37

In the above code we display the flash message if it is available If not we displaythe comment input form by rendering the partial view wwwrootblogprotectedviews

comment formphp

43 Managing Comments

Comment management includes updating deleting and approving comments These op-erations are implemented as actions in the CommentController class

431 Updating and Deleting Comments

The code generated by yiic for updating and deleting comments remains largely un-changed

432 Approving Comments

When comments are newly created they are in pending approval status and need to beapproved in order to be visible to guest users Approving a comment is mainly aboutchanging the status column of the comment

We create an actionApprove() method in CommentController as follows

public function actionApprove()

if(Yiiapp()-gtrequest-gtisPostRequest)

$comment=$this-gtloadModel()

$comment-gtapprove()

$this-gtredirect(array(rsquoindexrsquo))

else

throw new CHttpException(400rsquoInvalid requestrsquo)

In the above when the approve action is invoked via a POST request we call the approve()

method defined in the Comment model to change the status We then redirect the userbrowser to the page displaying the post that this comment belongs to

We also modify the actionIndex() method of Comment to show all comments We wouldlike to see comments pending approved to show up first

public function actionIndex()

38 4 Comment Management

$dataProvider=new CActiveDataProvider(rsquoCommentrsquo array(

rsquocriteriarsquo=gtarray(

rsquowithrsquo=gtrsquopostrsquo

rsquoorderrsquo=gtrsquotstatus tcreate time DESCrsquo

)

))

$this-gtrender(rsquoindexrsquoarray(

rsquodataProviderrsquo=gt$dataProvider

))

Notice that in the above code because both tbl post and tbl comment have columns statusand create time we need to disambiguate the corresponding column reference by prefixingthem with table alias names As described in the guide the alias for the primary table ina relational query is always t Therefore we are prefixing t to the status and create time

columns in the above code

Like the post index view the index view for CommentController uses [CListView] to displaythe comment list which in turn uses the partial view wwwrootblogprotectedviews

comment viewphp to display the detail of each individual comment We will not go intodetails here Interested readers may refer to the corresponding file in the blog demowwwrootyiidemosblogprotectedviewscomment viewphp

Chapter 5

Portlets

51 Customizing Page Layout

Features like rdquothe most recent commentsrdquo rdquotag cloudrdquo are better to be implemented inportlets A portlet is a pluggable user interface component that renders a fragment ofHTML code In this section we describe how to set up the portlet architecture for ourblog application

Before we go into details about developing portlets we first need to adjust our page layoutso that the portlets can be placed in a side bar

A page layout determines how to share common sections (eg header footer) amongdifferent pages and how to embed contents that are specific for different pages In Yiia page layout is represented as a view under the protectedviewslayouts folder Whenwe call the render() method in a controller the rendering result will be automaticallyembedded into this layout view before it is displayed to end users The default layoutview is named as main which means the wwwrootblogprotectedviewslayoutsmain

php file in our blog application

We could directly modify the main layout to add a side bar However doing so wouldmake some irrelevant pages such as about contact pages to have a side bar which is notalways desirable For this reason we need different layouts for different pages

We thus create two different layouts column1 and column2 The former displays the pagein a single column while the latter displays two columns in the page one of which is theside bar We will use column2 layout for the post and comment pages and column1 forthe rest of the pages Because both column1 and column2 still share the same header andfooter sections we let them to share the main layout

Based on the above description we create the column1 layout as follows

ltphp $this-gtbeginContent(rsquolayoutsmainrsquo) gt

ltdiv class=containergt

40 5 Portlets

ltdiv id=contentgt

ltphp echo $content gt

ltdivgtlt-- content --gt

ltdivgt

ltphp $this-gtendContent() gt

and the column2 layout as follows

ltphp $this-gtbeginContent(rsquolayoutsmainrsquo) gt

ltdiv class=containergt

ltdiv class=span-18gt

ltdiv id=contentgt

ltphp echo $content gt

ltdivgtlt-- content --gt

ltdivgt

ltdiv class=span-6 lastgt

ltdiv id=sidebargt

portlets to be placed here

ltdivgtlt-- sidebar --gt

ltdivgt

ltdivgt

ltphp $this-gtendContent() gt

As we can see both column1 and column2 are enclosed in a pair of beginContent andendContent calls They essentially use the specified view layoutsmain to decorate theenclosed content In all these layout views the variable $content refers to the dynamiccontent to be embedded into the layout

Besides customizing the layout view file we also need to adjust the CSS file wwwroot

blogcssmaincss so that the overall appearance would look like what we see in the blogdemo We will not go into details here

Info The skeleton application generated by the yiic tool uses the BluePrint CSSframework It provides a nice font reset and a robust grid system For more detailson how to use this framework please refer to its documentation

After creating the new layouts we need to set the layout property of controllers to makeuse of them So far we have three controllers SiteController PostController andCommentController We set SiteController to use column1 as the default layout whilethe rest two of the controllers use column2 for example

class PostController extends Controller

52 Creating User Menu Portlet 41

public $layout=rsquocolumn2rsquo

Tip If some view of the controller does not use the same layout as declaredby the layout property it may customize it in the view by a simple assignment$this-gtlayout=rsquonewlayoutrsquo

52 Creating User Menu Portlet

Based on the requirements analysis we need three different portlets the rdquouser menurdquoportlet the rdquotag cloudrdquo portlet and the rdquorecent commentsrdquo portlet We will develop theseportlets by extending the [CPortlet] widget provided by Yii

In this section we will develop our first concrete portlet - the user menu portlet whichdisplays a list of menu items that are only available to authenticated users The menucontains four items

bull Approve Comments a hyperlink that leads to a list of comments pending approval

bull Create New Post a hyperlink that leads to the post creation page

bull Manage Posts a hyperlink that leads to the post management page

bull Logout a link button that would log out the current user

521 Creating UserMenu Class

We create the UserMenu class to represent the logic part of the user menu portlet Theclass is saved in the file wwwrootblogprotectedcomponentsUserMenuphp which has thefollowing content

Yiiimport(rsquoziiwidgetsCPortletrsquo)

class UserMenu extends CPortlet

public function init()

$this-gttitle=CHtmlencode(Yiiapp()-gtuser-gtname)

parentinit()

42 5 Portlets

protected function renderContent()

$this-gtrender(rsquouserMenursquo)

The UserMenu class extends from the CPortlet class that we created previously It overridesboth the init() method and the renderContent() method of CPortlet The former setsthe portlet title to be the name of the current user the latter generates the portlet bodycontent by rendering a view named userMenu

Tip Notice that we have to explicitly include the CPortlet class by calling Yii

import() before we refer to it the first time This is because CPortlet is part of thezii project ndash the official extension library for Yii For performance considerationclasses in this project are not listed as core classes Therefore we have to import itbefore we use it the first time

522 Creating userMenu View

Next we create the userMenu view which is saved in the file wwwrootblogprotected

componentsviewsuserMenuphp

ltulgt

ltligtltphp echo CHtmllink(rsquoCreate New Postrsquoarray(rsquopostcreatersquo)) gtltligt

ltligtltphp echo CHtmllink(rsquoManage Postsrsquoarray(rsquopostadminrsquo)) gtltligt

ltligtltphp echo CHtmllink(rsquoApprove Commentsrsquoarray(rsquocommentindexrsquo))

rsquo (rsquo Commentmodel()-gtpendingCommentCount rsquo)rsquo gtltligt

ltligtltphp echo CHtmllink(rsquoLogoutrsquoarray(rsquositelogoutrsquo)) gtltligt

ltulgt

Info By default view files for a widget should be placed under the views sub-directory of the directory containing the widget class file The file name must bethe same as the view name

523 Using UserMenu Portlet

It is time for us to make use of our newly completed UserMenu portlet We modify thelayout view file wwwrootblogprotectedviewslayoutscolumn2php as follows

53 Creating Tag Cloud Portlet 43

ltdiv id=sidebargt

ltphp if(Yiiapp()-gtuser-gtisGuest) $this-gtwidget(rsquoUserMenursquo) gt

ltdivgt

In the above we call the widget() method to generate and execute an instance of theUserMenu class Because the portlet should only be displayed to authenticated users weonly call widget() when the isGuest property of the current user is false (meaning theuser is authenticated)

524 Testing UserMenu Portlet

Letrsquos test what we have so far

1 Open a browser window and enter the URL httpwwwexamplecomblogindex

php Verify that there is nothing displayed in the side bar section of the page

2 Click on the Login hyperlink and fill out the login form to login If successful verifythat the UserMenu portlet appears in the side bar and the portlet has the usernameas its title

3 Click on the rsquoLogoutrsquo hyperlink in the UserMenu portlet Verify that the logout actionis successful and the UserMenu portlet disappears

525 Summary

What we have created is a portlet that is highly reusable We can easily reuse it in adifferent project with little or no modification Moreover the design of this portlet followsclosely the philosophy that logic and presentation should be separated While we did notpoint this out in the previous sections such practice is used nearly everywhere in a typicalYii application

53 Creating Tag Cloud Portlet

Tag cloud displays a list of post tags with visual decorations hinting the popularity of eachindividual tag

531 Creating TagCloud Class

We create the TagCloud class in the file wwwrootblogprotectedcomponentsTagCloud

php The file has the following content

44 5 Portlets

Yiiimport(rsquoziiwidgetsCPortletrsquo)

class TagCloud extends CPortlet

public $title=rsquoTagsrsquo

public $maxTags=20

protected function renderContent()

$tags=Tagmodel()-gtfindTagWeights($this-gtmaxTags)

foreach($tags as $tag=gt$weight)

$link=CHtmllink(CHtmlencode($tag) array(rsquopostindexrsquorsquotagrsquo=gt$tag))

echo CHtmltag(rsquospanrsquo array(

rsquoclassrsquo=gtrsquotagrsquo

rsquostylersquo=gtfont-size$weightpt) $link)n

Unlike the UserMenu portlet the TagCloud portlet does not use a view Instead its presen-tation is done in the renderContent() method This is because the presentation does notcontain much HTML tags

We display each tag as a hyperlink to the post index page with the corresponding tagparameter The font size of each tag link is adjusted according to their relative weightamong other tags If a tag has higher frequency value than the other it will have a biggerfont size

532 Using TagCloud Portlet

Usage of the TagCloud portlet is very simple We modify the layout file wwwrootblog

protectedviewslayoutsmainphp as follows

ltdiv id=sidebargt

ltphp if(Yiiapp()-gtuser-gtisGuest) $this-gtwidget(rsquoUserMenursquo) gt

ltphp $this-gtwidget(rsquoTagCloudrsquo array(

rsquomaxTagsrsquo=gtYiiapp()-gtparams[rsquotagCloudCountrsquo]

)) gt

ltdivgt

54 Creating Recent Comments Portlet 45

54 Creating Recent Comments Portlet

In this section we create the last portlet that displays a list of comments recently pub-lished

541 Creating RecentComments Class

We create the RecentComments class in the file wwwrootblogprotectedcomponentsRecentCommentsphp The file has the following content

Yiiimport(rsquoziiwidgetsCPortletrsquo)

class RecentComments extends CPortlet

public $title=rsquoRecent Commentsrsquo

public $maxComments=10

public function getRecentComments()

return Commentmodel()-gtfindRecentComments($this-gtmaxComments)

protected function renderContent()

$this-gtrender(rsquorecentCommentsrsquo)

In the above we invoke the findRecentComments method which is defined in the Comment

class as follows

class Comment extends CActiveRecord

public function findRecentComments($limit=10)

return $this-gtwith(rsquopostrsquo)-gtfindAll(array(

rsquoconditionrsquo=gtrsquotstatus=rsquoselfSTATUS APPROVED

rsquoorderrsquo=gtrsquotcreate time DESCrsquo

rsquolimitrsquo=gt$limit

))

46 5 Portlets

542 Creating recentComments View

The recentComments view is saved in the file wwwrootblogprotectedcomponentsviews

recentCommentsphp It simply displays every comment returned by the RecentComments

getRecentComments() method

543 Using RecentComments Portlet

We modify the layout file wwwrootblogprotectedviewslayoutsmainphp to embedthis last portlet

ltdiv id=sidebargt

ltphp if(Yiiapp()-gtuser-gtisGuest) $this-gtwidget(rsquoUserMenursquo) gt

ltphp $this-gtwidget(rsquoTagCloudrsquo array(

rsquomaxTagsrsquo=gtYiiapp()-gtparams[rsquotagCloudCountrsquo]

)) gt

ltphp $this-gtwidget(rsquoRecentCommentsrsquo array(

rsquomaxCommentsrsquo=gtYiiapp()-gtparams[rsquorecentCommentCountrsquo]

)) gt

ltdivgt

Chapter 6

Final Work

61 Beautifying URLs

The URLs linking various pages of our blog application currently look ugly For examplethe URL for the page showing a post looks like the following

indexphpr=postshowampid=1

In this section we describe how to beautify these URLs and make them SEO-friendlyOur goal is to be able to use the following URLs in the application

1 indexphppostsyii leads to the page showing a list of posts with tag yii

2 indexphppost2A+Test+Post leads to the page showing the detail of the postwith ID 2 whose title is A Test Post

3 indexphppostupdate1 leads to the page that allows updating the post with ID1

Note that in the second URL format we include the post title in the URL This is mainlyto make the URL SEO friendly It is said that search engines may also respect the wordsfound in a URL when it is being indexed

To achieve our goal we modify the application configuration as follows

return array(

rsquocomponentsrsquo=gtarray(

rsquourlManagerrsquo=gtarray(

rsquourlFormatrsquo=gtrsquopathrsquo

rsquorulesrsquo=gtarray(

48 6 Final Work

rsquopostltidd+gtlttitlegtrsquo=gtrsquopostviewrsquorsquopostslttaggtrsquo=gtrsquopostindexrsquo

)

)

)

)

In the above we configure the urlManager component by setting its urlFormat propertyto be path and adding a set of rules

The rules are used by urlManager to parse and create the URLs in the desired formatFor example the first rule says that if a URL indexphppostsyii is requested theurlManager component should be responsible to dispatch the request to the route post

index and generate a tag GET parameter with the value yii On the other hand whencreating a URL with the route postindex and parameter tag the urlManager componentwill also use this rule to generate the desired URL indexphppostsyii For this reasonwe say that urlManager is a two-way URL manager

The urlManager component can further beautify our URLs such as hiding indexphp inthe URLs appending suffix like html to the URLs We can obtain these features easilyby configuring various properties of urlManager in the application configuration For moredetails please refer to the Guide

62 Logging Errors

A production Web application often needs sophisticated logging for various events In ourblog application we would like to log the errors occurring when it is being used Sucherrors could be programming mistakes or usersrsquo misuse of the system Logging these errorswill help us to improve the blog application

We enable the error logging by modifying the application configuration as follows

return array(

rsquopreloadrsquo=gtarray(rsquologrsquo)

rsquocomponentsrsquo=gtarray(

rsquologrsquo=gtarray(

rsquoclassrsquo=gtrsquoCLogRouterrsquo

rsquoroutesrsquo=gtarray(

array(

rsquoclassrsquo=gtrsquoCFileLogRoutersquo

rsquolevelsrsquo=gtrsquoerror warningrsquo

63 Final Tune-up and Deployment 49

)

)

)

)

)

With the above configuration if an error or warning occurs detailed information will belogged and saved in a file located under the directory wwwrootblogprotectedruntime

The log component offers more advanced features such as sending log messages to a listof email addresses displaying log messages in JavaScript console window etc For moredetails please refer to the Guide

63 Final Tune-up and Deployment

We are close to finish our blog application Before deployment we would like to do sometune-ups

631 Changing Home Page

We change to use the post list page as the home page We modify the application config-uration as follows

return array(

rsquodefaultControllerrsquo=gtrsquopostrsquo

)

Tip Because PostController already declares list to be its default action whenwe access the home page of the application we will see the result generated by thelist action of the post controller

632 Enabling Schema Caching

Because ActiveRecord relies on the metadata about tables to determine the column infor-mation it takes time to read the metadata and analyze it This may not be a problemduring development stage but for an application running in production mode it is a totalwaste of time if the database schema does not change Therefore we should enable theschema caching by modifying the application configuration as follows

50 6 Final Work

return array(

rsquocomponentsrsquo=gtarray(

rsquocachersquo=gtarray(

rsquoclassrsquo=gtrsquoCDbCachersquo

)

rsquodbrsquo=gtarray(

rsquoclassrsquo=gtrsquosystemdbCDbConnectionrsquo

rsquoconnectionStringrsquo=gtrsquosqlitewwwrootblogprotecteddatablogdbrsquo

rsquoschemaCachingDurationrsquo=gt3600

)

)

)

In the above we first add a cache component which uses a default SQLite database as thecaching storage If our server is equipped with other caching extensions such as APCwe could change to use them as well We also modify the db component by setting itsschemaCachingDuration property to be 3600 which means the parsed database schemadata can remain valid in cache for 3600 seconds

633 Disabling Debugging Mode

We modify the entry script file wwwrootblogindexphp by removing the line definingthe constant YII DEBUG This constant is useful during development stage because it allowsYii to display more debugging information when an error occurs However when theapplication is running in production mode displaying debugging information is not agood idea because it may contain sensitive information such as where the script file islocated and the content in the file etc

634 Deploying the Application

The final deployment process manly involves copying the directory wwwrootblog to thetarget directory The following checklist shows every needed step

1 Install Yii in the target place if it is not available

2 Copy the entire directory wwwrootblog to the target place

3 Edit the entry script file indexphp by pointing the $yii variable to the new Yiibootstrap file

4 Edit the file protectedyiicphp by setting the $yiic variable to be the new Yiiyiicphp file

64 Future Enhancements 51

5 Change the permission of the directories assets and protectedruntime so that theyare writable by the Web server process

64 Future Enhancements

641 Using a Theme

Without writing any code our blog application is already themeable To use a themewe mainly need to develop the theme by writing customized view files in the theme Forexample to use a theme named classic that uses a different page layout we would createa layout view file wwwrootblogthemesclassicviewslayoutsmainphp We also needto change the application configuration to indicate our choice of the classic theme

return array(

rsquothemersquo=gtrsquoclassicrsquo

)

642 Internationalization

We may also internationalize our blog application so that its pages can be displayed indifferent languages This mainly involves efforts in two aspects

First we may create view files in different languages For example for the list pageof PostController we can create a view file wwwrootblogprotectedviewspostzh cn

listphp When the application is configured to use simplified Chinese (the language codeis zh cn) Yii will automatically use this new view file instead of the original one

Second we may create message translations for those messages generated by code Themessage translations should be saved as files under the directory wwwrootblogprotected

messages We also need to modify the code where we use text strings by enclosing themin the method call Yiit()

For more details about internationalization please refer to the Guide

643 Improving Performance with Cache

While the Yii framework itself is very efficient it is not necessarily true that an applicationwritten in Yii efficient There are several places in our blog application that we can improvethe performance For example the tag clould portlet could be one of the performancebottlenecks because it involves complex database query and PHP logic

52 6 Final Work

We can make use of the sophisticated caching feature provided by Yii to improve theperformance One of the most useful components in Yii is COutputCache which caches afragment of page display so that the underlying code generating the fragment does not needto be executed for every request For example in the layout file wwwrootblogprotected

viewslayoutsmainphp we can enclose the tag cloud portlet with COutputCache

ltphp if($this-gtbeginCache(rsquotagCloudrsquo array(rsquodurationrsquo=gt3600))) gt

ltphp $this-gtwidget(rsquoTagCloudrsquo array(

rsquomaxTagsrsquo=gtYiiapp()-gtparams[rsquotagCloudCountrsquo]

)) gt

ltphp $this-gtendCache() gt

With the above code the tag cloud display will be served from cache instead of beinggenerated on-the-fly for every request The cached content will remain valid in cache for3600 seconds

644 Adding New Features

Our blog application only has very basic functionalities To become a complete blogsystem more features are needed for example calendar portlet email notifications postcategorization archived post portlet and so on We will leave the implementation of thesefeatures to interested readers

  • Contents
  • License
  • Getting Started
    • Building a Blog System using Yii
    • Testdriving with Yii
      • Installing Yii
      • Creating Skeleton Application
      • Application Workflow
        • Requirements Analysis
        • Overall Design
          • Initial Prototyping
            • Setting Up Database
              • Creating Database
              • Establishing Database Connection
                • Scaffolding
                • Authenticating User
                • Summary
                  • Post Management
                    • Customizing Post Model
                      • Customizing rules() Method
                      • Customizing relations() Method
                      • Representing Status in Text
                        • Creating and Updating Posts
                          • Customizing Access Control
                          • Customizing create and update Operations
                            • Displaying Posts
                              • Customizing view Operation
                              • Customizing index Operation
                                • Managing Posts
                                  • Listing Posts in Tabular View
                                  • Deleting Posts
                                      • Comment Management
                                        • Customizing Comment Model
                                          • Customizing rules() Method
                                          • Customizing attributeLabels() Method
                                          • Customizing Saving Process
                                            • Creating and Displaying Comments
                                              • Displaying Comments
                                              • Creating Comments
                                                • Managing Comments
                                                  • Updating and Deleting Comments
                                                  • Approving Comments
                                                      • Portlets
                                                        • Customizing Page Layout
                                                        • Creating User Menu Portlet
                                                          • Creating UserMenu Class
                                                          • Creating userMenu View
                                                          • Using UserMenu Portlet
                                                          • Testing UserMenu Portlet
                                                          • Summary
                                                            • Creating Tag Cloud Portlet
                                                              • Creating TagCloud Class
                                                              • Using TagCloud Portlet
                                                                • Creating Recent Comments Portlet
                                                                  • Creating RecentComments Class
                                                                  • Creating recentComments View
                                                                  • Using RecentComments Portlet
                                                                      • Final Work
                                                                        • Beautifying URLs
                                                                        • Logging Errors
                                                                        • Final Tune-up and Deployment
                                                                          • Changing Home Page
                                                                          • Enabling Schema Caching
                                                                          • Disabling Debugging Mode
                                                                          • Deploying the Application
                                                                            • Future Enhancements
                                                                              • Using a Theme
                                                                              • Internationalization
                                                                              • Improving Performance with Cache
                                                                              • Adding New Features
Page 19: Yii Blog 1.1.0 Demo Tutor

12 2 Initial Prototyping

Info Some PHP installations may use a different phpini file for command line(CLI) PHP parser As a result when running the above yiic commands youmay encounter errors like rdquoYiiBaseinclude(PDOphp) failed to open streamrdquoor rdquocould not find driverrdquo Please double check your CLI PHP configuration byexecuting the following command

php -r phpinfo()

The result of the above command will show which phpini file is being used andwhich extensions are loaded If a wrong phpini file is used you may use thefollowing command to explicitly specify the correct phpini to use

php -c phpini wwwrootyiiframeworkyiicphp shell wwwrootblogindexphp

The commands above accomplish two tasks First the model command generates a modelclass file for each table in the blog database Second the crud commands generate thecode needed by the CRUD operations for the Post and Comment models

Tip The command model generates a model class for every table in the databaseSometimes we probably donrsquot want to do so (eg the database contains some irrel-evant tables) In this case we can create model classes one by one For exampleto create the User model we can use the command model User The model com-mand also has some more advanced usages For more details execute help model

command

We can test the generated code by accessing the following URLs

httpwwwexamplecomblogindexphpr=post

httpwwwexamplecomblogindexphpr=comment

Notice that the post and comment features implemented by the generated code are com-pletely independent of each other Also when creating a new post or comment we arerequired to enter information such as author id and create time which in real applica-tion should be set by the program Donrsquot worry We will fix these problems in the nextmilestones For now we should be fairly satisfied as this prototype already contains mostfeatures that we need to implement for the blog application

To prepare for the next milestones letrsquos take a closer look at the files generated by theabove commands All the files are generated under wwwrootblogprotected For conve-nience we group them into model files controller files and view files

22 Scaffolding 13

bull model files

ndash modelsUserphp contains the User class that extends from CActiveRecord andcan be used to access the tbl user database table

ndash modelsPostphp contains the Post class that extends from CActiveRecord andcan be used to access the tbl post database table

ndash modelsTagphp contains the Tag class that extends from CActiveRecord andcan be used to access the tbl tag database table

ndash modelsCommentphp contains the Comment class that extends from CActiveRe-cord and can be used to access the tbl comment database table

ndash modelsLookupphp contains the Lookup class that extends from CActiveRecordand can be used to access the tbl lookup database table

bull controller file

ndash controllersPostControllerphp contains the PostController class which is thecontroller in charge of all CRUD operations about posts

ndash controllersCommentControllerphp contains the CommentController class whichis the controller in charge of all CRUD operations about comments

bull view files

ndash viewspostcreatephp is the view file that shows an HTML form to create anew post

ndash viewspostupdatephp is the view file that shows an HTML form to update anexisting post

ndash viewspostviewphp is the view file that displays the detailed information of apost

ndash viewspostindexphp is the view file that displays a list of posts

ndash viewspostadminphp is the view file that displays posts in a table with ad-ministrative commands

ndash viewspost formphp is the partial view file embedded in viewspostcreate

php and viewspostupdatephp It displays the HTML form for collecting postinformation

ndash viewspost viewphp is the partial view file used by viewspostindexphp Itdisplays the brief view of a single post

ndash a similar set of view files are also generated for comment

14 2 Initial Prototyping

In order to understand better how the above files are used we show in the following theworkflow that occurs in the blog application when displaying a list of posts

1 The user requests the URL httpwwwexamplecomblogindexphpr=posts

2 The entry script is executed by the Web server which creates and initializes anapplication instance to handle the request

3 The application creates an instance of PostController and executes it

4 The PostController instance executes the index action by calling its actionIndex()

method Note that index is the default action if the user does not specify an actionto execute in the URL

5 The actionIndex() method queries database to bring back the list of recent posts

6 The actionIndex() method renders the index view with the post data

23 Authenticating User

Our blog application needs to differentiate between the system owner and guest usersTherefore we need to implement the user authentication feature

As you may have found that the skeleton application already provides user authenticationby checking if the username and password are both demo or admin In this section wewill modify the corresponding code so that the authentication is done against the User

database table

User authentication is performed in a class implementing the IUserIdentity interface Theskeleton application uses the UserIdentity class for this purpose The class is stored inthe file wwwrootblogprotectedcomponentsUserIdentityphp

Tip By convention the name of a class file must be the same as the correspondingclass name suffixed with the extension php Following this convention one canrefer to a class using a path alias For example we can refer to the UserIdentity

class with the alias applicationcomponentsUserIdentity Many APIs in Yiican recognize path aliases (eg YiicreateComponent()) and using path aliasesavoids the necessity of embedding absolute file paths in the code The existence ofthe latter often causes trouble when we deploy an application

We modify the UserIdentity class as follows

23 Authenticating User 15

ltphp

class UserIdentity extends CUserIdentity

private $ id

public function authenticate()

$username=strtolower($this-gtusername)

$user=Usermodel()-gtfind(rsquoLOWER(username)=rsquoarray($username))

if($user===null)

$this-gterrorCode=selfERROR USERNAME INVALID

else if($user-gtvalidatePassword($this-gtpassword))

$this-gterrorCode=selfERROR PASSWORD INVALID

else

$this-gt id=$user-gtid

$this-gtusername=$user-gtusername

$this-gterrorCode=selfERROR NONE

return $this-gterrorCode==selfERROR NONE

public function getId()

return $this-gt id

In the authenticate() method we use the User class to look for a row in the User tablewhose username column is the same as the given username in a case-insensitive mannerRemember that the User class was created using the yiic tool in the prior section Becausethe User class extends from CActiveRecord we can exploit the ActiveRecord feature toaccess the User table in an OOP fashion

In order to check if the user has entered a valid password we invoke the validatePassword

method of the User class We need to modify the file wwwrootblogprotectedmodels

Userphp as follows Note that instead of storing the plain password in the database westore the hash result of the password and a randomly generated salt key When validatingthe user-entered password we should compare the hash results instead

class User extends CActiveRecord

public function validatePassword($password)

return $this-gthashPassword($password$this-gtsalt)===$this-gtpassword

16 2 Initial Prototyping

public function hashPassword($password$salt)

return md5($salt$password)

In the UserIdentity class we also override the getId() method which returns the id valueof the user found in the User table The parent implementation would return the usernameinstead Both the username and id properties will be stored in the user session and maybe accessed via Yiiapp()-gtuser from anywhere in our code

Tip In the UserIdentity class we reference the class CUserIdentity without ex-plicitly including the corresponding class file This is because CUserIdentity is acore class provided by the Yii framework Yii will automatically include the classfile for any core class when it is referenced for the first time

We also do the same with the User class This is because the User class file is placedunder the directory wwwrootblogprotectedmodels which has been added tothe PHP include path according to the following lines found in the applicationconfiguration

return array(

rsquoimportrsquo=gtarray(

rsquoapplicationmodelsrsquo

rsquoapplicationcomponentsrsquo

)

)

The above configuration says that any class whose class file is located under either wwwrootblogprotectedmodels or wwwrootblogprotectedcomponents willbe automatically included when the class is referenced for the first time

The UserIdentity class is mainly used by the LoginForm class to authenticate a user basedon the username and password input collected from the login page The following codefragment shows how UserIdentity is used

$identity=new UserIdentity($username$password)

$identity-gtauthenticate()

switch($identity-gterrorCode)

24 Summary 17

case UserIdentityERROR NONE

Yiiapp()-gtuser-gtlogin($identity)

break

Info People often get confused about identity and the user application componentThe former represents a way of performing authentication while the latter is usedto represent the information related with the current user An application can onlyhave one user component but it can have one or several identity classes dependingon what kind of authentication it supports Once authenticated an identity instancemay pass its state information to the user component so that they are globallyaccessible via user

To test the modified UserIdentity class we can browse the URL httpwwwexample

comblogindexphp and try logging in with the username and password that we store inthe User table If we use the database provided by the blog demo we should be able tologin with username demo and password demo Note that this blog system does not providethe user management feature As a result a user cannot change his account or create anew one through the Web interface The user management feature may be considered asa future enhancement to the blog application

24 Summary

We have completed the milestone 1 Letrsquos summarize what we have done so far

1 We identified the requirements to be fulfilled

2 We installed the Yii framework

3 We created a skeleton application

4 We designed and created the blog database

5 We modified the application configuration by adding the database connection

6 We generated the code that implements the basic CRUD operations for both postsand comments

7 We modified the authentication method to check against the tbl user table

18 2 Initial Prototyping

For a new project most of the time will be spent in step 1 and 4 for this first milestone

Although the code generated by the yiic tool implements fully functional CRUD oper-ations for a database table it often needs to be modified in practical applications Forthis reason in the next two milestone our job is to customize the generated CRUD codeabout posts and comments so that it reaches our initial requirements

In general we first modify the model class file by adding appropriate validation rules anddeclaring relational objects We then modify the controller action and view code for eachindividual CRUD operation

Chapter 3

Post Management

31 Customizing Post Model

The Post model class generated by the yiic tool mainly needs to be modified in two places

bull the rules() method specifies the validation rules for the model attributes

bull the relations() method specifies the related objects

Info A model consists of a list of attributes each associated with a column in thecorresponding database table Attributes can be declared explicitly as class membervariables or implicitly without any declaration

311 Customizing rules() Method

We first specify the validation rules which ensure the attribute values entered by usersare correct before they are saved to the database For example the status attribute ofPost should be an integer 1 2 or 3 The yiic tool also generates validation rules for eachmodel However these rules are based on the table column information and may not beappropriate

Based on the requirement analysis we modify the rules() method as follows

public function rules()

return array(

array(rsquotitle content statusrsquo rsquorequiredrsquo)

array(rsquotitlersquo rsquolengthrsquo rsquomaxrsquo=gt128)

array(rsquostatusrsquo rsquoinrsquo rsquorangersquo=gtarray(123))

array(rsquotagsrsquo rsquomatchrsquo rsquopatternrsquo=gtrsquo^[ws]+$rsquorsquomessagersquo=gtrsquoTags can only contain word charactersrsquo)

20 3 Post Management

array(rsquotagsrsquo rsquonormalizeTagsrsquo)

)

In the above we specify that the title content and status attributes are required thelength of title should not exceed 128 the status attribute value should be 1 (draft) 2(published) or 3 (archived) and the tags attribute should only contain word charactersand commas In addition we use normalizeTags to normalize the user-entered tags so thatthe tags are unique and properly separated with commas

The validators such as required length in and match are all built-in validators providedby Yii The normalizeTags validator is a method-based validator that we need to definein the Post class For more information about how to specify validation rules please referto the Guide

public function normalizeTags($attribute$params)

$this-gttags=Tagarray2string(array unique(Tagstring2array($this-gttags)))

The rules declared in this method are executed one by one when we call the validate() orsave() method of the model instance

Note It is very important to remember that attributes appearing in rules() mustbe those to be entered by end users Other attributes such as id and create time

in the Post model which are set by our code or database should not be in rules()For more details please refer to Securing Attribute Assignments

After making these changes we can visit the post creation page again to verify that thenew validation rules are taking effect

312 Customizing relations() Method

Lastly we customize the relations() method to specify the related objects of a post Bydeclaring these related objects in relations() we can exploit the powerful RelationalActiveRecord (RAR) feature to access the related object information of a post such asits author and comments without the need to write complex SQL JOIN statements

We customize the relations() method as follows

31 Customizing Post Model 21

public function relations()

return array(

rsquoauthorrsquo =gt array(selfBELONGS TO rsquoUserrsquo rsquoauthor idrsquo)

rsquocommentsrsquo =gt array(selfHAS MANY rsquoCommentrsquo rsquopost idrsquo

rsquoconditionrsquo=gtrsquocommentsstatus=rsquoCommentSTATUS APPROVED

rsquoorderrsquo=gtrsquocommentscreate time DESCrsquo)

rsquocommentCountrsquo =gt array(selfSTAT rsquoCommentrsquo rsquopost idrsquo

rsquoconditionrsquo=gtrsquostatus=rsquoCommentSTATUS APPROVED)

)

The above relations state that

bull A post belongs to an author whose class is User and the relationship is establishedbased on the author id attribute value of the post

bull A post has many comments whose class is Comment and the relationship is establishedbased on the post id attribute value of the comments These comments should besorted according to their creation time and the comments must be approved

bull The commentCount relation is a bit special as it returns back an aggregation resultwhich is about how many comments the post has

With the above relation declaration we can easily access the author and comments of apost like the following

$author=$post-gtauthor

echo $author-gtusername

$comments=$post-gtcomments

foreach($comments as $comment)

echo $comment-gtcontent

For more details about how to declare and use relations please refer to the Guide

313 Representing Status in Text

Because the status of a post is stored as an integer in the database we need to provide atextual representation so that it is more intuitive when being displayed to end users In alarge system the similar requirement is very common

22 3 Post Management

As a generic solution we use the tbl lookup table to store the mapping between integervalues and textual representations that are needed by other data objects We modify theLookup model class as follows to more easily access the textual data in the table

class Lookup extends CActiveRecord

private static $ items=array()

public static function items($type)

if(isset(self$ items[$type]))

selfloadItems($type)

return self$ items[$type]

public static function item($type$code)

if(isset(self$ items[$type]))

selfloadItems($type)

return isset(self$ items[$type][$code]) self$ items[$type][$code] false

private static function loadItems($type)

self$ items[$type]=array()

$models=selfmodel()-gtfindAll(array(

rsquoconditionrsquo=gtrsquotype=typersquo

rsquoparamsrsquo=gtarray(rsquotypersquo=gt$type)

rsquoorderrsquo=gtrsquopositionrsquo

))

foreach($models as $model)

self$ items[$type][$model-gtcode]=$model-gtname

Our new code mainly provides two static methods Lookupitems() and Lookupitem()The former returns a list of strings belonging to the specified data type while the latterreturns a particular string for the given data type and data value

Our blog database is pre-populated with two lookup types PostStatus and CommentStatusThe former refers to the possible post statuses while the latter the comment statuses

In order to make our code easier to read we also declare a set of constants to representthe status integer values We should use these constants through our code when referringto the corresponding status values

32 Creating and Updating Posts 23

class Post extends CActiveRecord

const STATUS DRAFT=0

const STATUS PUBLISHED=1

const STATUS ARCHIVED=2

Therefore we can call Lookupitems(rsquoPostStatusrsquo) to get the list of possible post statuses(text strings indexed by the corresponding integer values) and call Lookupitem(rsquoPostStatusrsquoPostSTATUS PUBLISHED) to get the string representation of the published status

32 Creating and Updating Posts

With the Post model ready we need to fine-tune the actions and views for the controllerPostController In this section we first customize the access control of CRUD operationswe then modify the code implementing the create and update operations

321 Customizing Access Control

The first thing we want to do is to customize the access control because the code generatedby yiic does not fit our needs

We modify the accessRules() method in the file wwwrootblogprotectedcontrollers

PostControllerphp as follows

public function accessRules()

return array(

array(rsquoallowrsquo allow all users to perform rsquolistrsquo and rsquoshowrsquo actions

rsquoactionsrsquo=gtarray(rsquoindexrsquo rsquoviewrsquo)

rsquousersrsquo=gtarray(rsquorsquo)

)

array(rsquoallowrsquo allow authenticated users to perform any action

rsquousersrsquo=gtarray(rsquorsquo)

)

array(rsquodenyrsquo deny all users

rsquousersrsquo=gtarray(rsquorsquo)

)

)

The above rules state that all users can access the index and view actions and authenti-cated users can access any actions including the admin action The user should be denied

24 3 Post Management

access in any other scenario Note that these rules are evaluated in the order they arelisted here The first rule matching the current context makes the access decision Forexample if the current user is the system owner who tries to visit the post creation pagethe second rule will match and it will give the access to the user

322 Customizing create and update Operations

The create and update operations are very similar They both need to display an HTMLform to collect user inputs validate them and save them into database The main differ-ence is that the update operation will pre-populate the form with the existing post datafound in the database For this reason the yiic tool generates a partial view wwwroot

blogprotectedviewspost formphp that is embedded in both the create and update

views to render the needed HTML form

We first change the formphp file so that the HTML form only collects the inputs wewant title content tags and status We use plain text fields to collect inputs for thefirst three attributes and a dropdown list to collect input for status The dropdown listoptions are the text displays of the possible post statuses

ltphp echo CHtmlactiveDropDownList($postrsquostatusrsquoLookupitems(rsquoPostStatusrsquo)) gt

In the above we call Lookupitems(rsquoPostStatusrsquo) to bring back the list of post statuses

We then modify the Post class so that it can automatically set some attributes (eg create

time author id) before a post is saved to the database We override the beforeSave()

method as follows

protected function beforeSave()

if(parentbeforeSave())

if($this-gtisNewRecord)

$this-gtcreate time=$this-gtupdate time=time()

$this-gtauthor id=Yiiapp()-gtuser-gtid

else

$this-gtupdate time=time()

return true

else

return false

33 Displaying Posts 25

When we save a post we want to update the tbl tag table to reflect the change of tagfrequencies We can do this work in the afterSave() method which is automaticallyinvoked by Yii after a post is successfully saved into the database

protected function afterSave()

parentafterSave()

Tagmodel()-gtupdateFrequency($this-gt oldTags $this-gttags)

private $ oldTags

protected function afterFind()

parentafterFind()

$this-gt oldTags=$this-gttags

In the implementation because we want to detect if the user changes the tags in case he isupdating an existing post we need to know what the old tags are For this reason we alsowrite the afterFind() method to keep the old tags in the variable oldTags The methodafterFind() is invoked automatically by Yii when an AR record is populated with thedata from database

We are not going to give details of the TagupdateFrequency() method here Interestedreaders may refer to the file wwwrootyiidemosblogprotectedmodelsTagphp

33 Displaying Posts

In our blog application a post may be displayed among a list of posts or by itself Theformer is implemented as the index operation while the latter the view operation In thissection we customize both operations to fulfill our initial requirements

331 Customizing view Operation

The view operation is implemented by the actionView() method in PostController Itsdisplay is generated by the view view with the view file wwwrootblogprotectedviews

postviewphp

Below is the relevant code implementing the view operation in PostController

public function actionView()

26 3 Post Management

$this-gtrender(rsquoviewrsquoarray(

$post=$this-gtloadModel()

$this-gtrender(rsquoviewrsquoarray(

rsquomodelrsquo=gt$post

))

))

private $ model

public function loadModel()

if($this-gt model===null)

if(isset($ GET[rsquoidrsquo]))

if(Yiiapp()-gtuser-gtisGuest)

$condition=rsquostatus=rsquoPostSTATUS PUBLISHED

rsquo OR status=rsquoPOSTSTATUS ARCHIVED

else

$condition=rsquorsquo

$this-gt model=Postmodel()-gtfindbyPk($ GET[rsquoidrsquo] $condition)

if($this-gt model===null)

throw new CHttpException(404rsquoThe requested page does not existrsquo)

return $this-gt model

Our change mainly lies in the loadModel() method In this method we query the Post

table according to the id GET parameter If the post is not found or if it is not publishedor archived (when the user is a guest) we will throw a 404 HTTP error Otherwise thepost object is returned to actionView() which in turn passes the post object to the viewscript for further display

Tip Yii captures HTTP exceptions (instances of CHttpException) and displaysthem in either predefined templates or customized error views The skeleton appli-cation generated by yiic already contains a customized error view in wwwroot

blogprotectedviewssiteerrorphp We can modify this file if we want tofurther customize the error display

The change in the view script is mainly about ajdusting the formatting and styles of thepost display We will not go into details here Interested readers may refer to wwwroot

blogprotectedviewspostviewphp

33 Displaying Posts 27

332 Customizing index Operation

Like the view operation we customize the index operation in two places the actionIndex()

method in PostController and the view file wwwrootblogprotectedviewspostindex

php We mainly need to add the support for displaying a list of posts that are associatedwith a specified tag

Below is the modified actionIndex() method in PostController

public function actionIndex()

$criteria=new CDbCriteria(array(

rsquoconditionrsquo=gtrsquostatus=rsquoPostSTATUS PUBLISHED

rsquoorderrsquo=gtrsquoupdate time DESCrsquo

rsquowithrsquo=gtrsquocommentCountrsquo

))

if(isset($ GET[rsquotagrsquo]))

$criteria-gtaddSearchCondition(rsquotagsrsquo$ GET[rsquotagrsquo])

$dataProvider=new CActiveDataProvider(rsquoPostrsquo array(

rsquopaginationrsquo=gtarray(

rsquopageSizersquo=gtYiiapp()-gtparams[rsquopostsPerPagersquo]

)

rsquocriteriarsquo=gt$criteria

))

$this-gtrender(rsquoindexrsquoarray(

rsquodataProviderrsquo=gt$dataProvider

))

In the above we first create a query criteria for retrieving post list The criteria statesthat only published posts should be returned and they should be sorted according to theirupdate time in descending order Because when displaying a post in the list we want toshow how many comments the post has received in the criteria we also specify to bringback commentCount which if you remember is a relation declared in Postrelations()

In case when a user wants to see posts with a specific tag we would add a search conditionto the criteria to look for the specified tag

Using the query criteria we create a data provider which mainly serves for three purposesFirst it does pagination of the data when too many results may be returned Herewe customize the pagination by setting the page size to be an application parameterpostsPerPage Second it does sorting according to the user request And finally it feedsthe paginated and sorted data to widgets or view code for presentation

28 3 Post Management

After we finish with actionIndex() we modify the index view as follows Our change ismainly about adding the h1 header when the user specifies to display posts with a tag

ltphp if(empty($ GET[rsquotagrsquo])) gt

lth1gtPosts Tagged with ltigtltphp echo CHtmlencode($ GET[rsquotagrsquo]) gtltigtlth1gt

ltphp endif gt

ltphp $this-gtwidget(rsquoziiwidgetsCListViewrsquo array(

rsquodataProviderrsquo=gt$dataProvider

rsquoitemViewrsquo=gtrsquo viewrsquo

rsquotemplatersquo=gtitemsnpager)) gt

Note that in the above we use [CListView] to display the post list This widget requires apartial view to display the detail of each individual post Here we specify the partial viewto be view which means the file wwwrootblogprotectedviewspost viewphp In thisview script we can acccess the post instance being displayed via a local variable named$data

34 Managing Posts

Managing posts mainly refers to listing posts in an administrative view that allows us tosee posts with all statuses updating them and deleting them They are accomplished bythe admin operation and the delete operation respectively The code generated by yiic

does not need much modification Below we mainly explain how these two operations areimplemented

341 Listing Posts in Tabular View

The admin operation shows posts with all statuses in a tabular view The view supportssorting and pagination The following is the actionAdmin() method in PostController

public function actionAdmin()

$dataProvider=new CActiveDataProvider(rsquoPostrsquo array(

rsquosortrsquo=gtarray(

rsquodefaultOrderrsquo=gtrsquostatus update time DESCrsquo

)

))

$this-gtrender(rsquoadminrsquoarray(

rsquodataProviderrsquo=gt$dataProvider

))

34 Managing Posts 29

The above code is very similar to that in actionIndex() The main difference is in thequery criteria Here we do not specify any particular query criteria since we want to seeall posts We do customize the sort property of the data provider so that by default theposts are shown in the order of their statuses and update time

Below is the code for the admin view

lth1gtManage Postslth1gt

ltphp $this-gtwidget(rsquoziiwidgetsgridCGridViewrsquo array(

rsquodataProviderrsquo=gt$dataProvider

rsquocolumnsrsquo=gtarray(

array(

rsquonamersquo=gtrsquotitlersquo

rsquotypersquo=gtrsquorawrsquo

rsquovaluersquo=gtrsquoCHtmllink(CHtmlencode($data-gttitle) $data-gturl)rsquo

)

array(

rsquonamersquo=gtrsquostatusrsquo

rsquovaluersquo=gtrsquoLookupitem(PostStatus$data-gtstatus)rsquo

)

rsquocreate timedatetimersquo

rsquoupdate timedatetimersquo

array(

rsquoclassrsquo=gtrsquoCButtonColumnrsquo

)

)

)) gt

We use [CGridView] to display the posts It allows us to sort by a column and paginatethrough the posts if there are too many to be displayed in a single page Our change ismainly about how to display each column For example for the title column we specifythat it should be displayed as a hyperlink that points to the detailed view of the post

Tip When displaying text we call CHtmlencode() to encode HTML entities init This prevents from cross-site scripting attack

342 Deleting Posts

In the admin data grid there is a delete button in each row Clicking on the button shoulddelete the corresponding post Internally this triggers the delete action implemented asfollows

30 3 Post Management

public function actionDelete()

if(Yiiapp()-gtrequest-gtisPostRequest)

we only allow deletion via POST request

$this-gtloadModel()-gtdelete()

if(isset($ POST[rsquoajaxrsquo]))

$this-gtredirect(array(rsquoindexrsquo))

else

throw new CHttpException(400rsquoInvalid request Please do not repeat this request againrsquo)

The above code is the one generated by the yiic tool without any change We wouldlike to explain a little bit more about the checking on $ POST[rsquoajaxrsquo] The [CGridView]widget has a very nice feature that its sorting pagination and deletion operations are alldone in AJAX mode by default That means the whole page does not get reloaded if anyof the above operations is performed However it is also possible that the widget runs innon-AJAX mode (by setting its ajaxUpdate property to be false or disabling JavaScript onthe client side) It is necessary for the delete action to differentiate these two scenarios ifthe delete request is made via AJAX we should not redirect the user browser otherwisewe should

Deleting a post should also cause the deletion of all comments for that post In additionwe should also update the tbl tag table regarding the tags for the deleted post Both ofthese tasks can be achieved by writing an afterDelete method in the Post model class asfollows

protected function afterDelete()

parentafterDelete()

Commentmodel()-gtdeleteAll(rsquopost id=rsquo$this-gtid)

Tagmodel()-gtupdateFrequency($this-gttags rsquorsquo)

The above code is very straightforward it first deletes all those comments whose post id

is the same as the ID of the deleted post it then updates the tbl tag table for the tags

of the deleted post

34 Managing Posts 31

Tip We have to explicitly delete all comments for the deleted post here becauseSQLite does not really support foreign key constraints In a DBMS that supportsthis constraint (such as MySQL PostgreSQL) the foreign key constraint can be setup such that the DBMS automatically deletes the related comments if the post isdeleted In that case we no longer this explicit deletion call in our code

32 3 Post Management

Chapter 4

Comment Management

41 Customizing Comment Model

For the Comment model we mainly need to customize the rules() and attributeLabels()

methods The attributeLabels() method returns a mapping between attribute namesand attribute labels We do not need to touch relations() since the code generated bythe yiic tool is good enough

411 Customizing rules() Method

We first customize the validation rules generated by the yiic tool The following rules areused for comments

public function rules()

return array(

array(rsquocontent author emailrsquo rsquorequiredrsquo)

array(rsquoauthor email urlrsquo rsquolengthrsquo rsquomaxrsquo=gt128)

array(rsquoemailrsquorsquoemailrsquo)

array(rsquourlrsquorsquourlrsquo)

)

In the above we specify that the author email and content attributes are required thelength of author email and url cannot exceed 128 the email attribute must be a validemail address and the url attribute must be a valid URL

412 Customizing attributeLabels() Method

We then customize the attributeLabels() method to declare the label display for eachmodel attribute This method returns an array consisting of name-label pairs When wecall CHtmlactiveLabel() to display an attribute label

34 4 Comment Management

public function attributeLabels()

return array(

rsquoidrsquo =gt rsquoIdrsquo

rsquocontentrsquo =gt rsquoCommentrsquo

rsquostatusrsquo =gt rsquoStatusrsquo

rsquocreate timersquo =gt rsquoCreate Timersquo

rsquoauthorrsquo =gt rsquoNamersquo

rsquoemailrsquo =gt rsquoEmailrsquo

rsquourlrsquo =gt rsquoWebsitersquo

rsquopost idrsquo =gt rsquoPostrsquo

)

Tip If the label for an attribute is not declared in attributeLabels() an algo-rithm will be used to generate an appropriate label For example a label VerifyCode will be generated for attributes verify code or verifyCode

413 Customizing Saving Process

Because we want to record the creation time of a comment we override the beforeSave()

method of Comment like we do for the Post model

protected function beforeSave()

if(parentbeforeSave())

if($this-gtisNewRecord)

$this-gtcreate time=time()

return true

else

return false

42 Creating and Displaying Comments

In this section we implement the comment display and creation features

421 Displaying Comments

Instead of displaying and creating comments on individual pages we use the post detailpage (generated by the view action of PostController) Below the post content display

42 Creating and Displaying Comments 35

we display first a list of comments belonging to that post and then a comment creationform

In order to display comments on the post detail page we modify the view script wwwrootblogprotectedviewspostviewphp as follows

ltdiv id=commentsgt

ltphp if($model-gtcommentCountgt=1) gt

lth3gt

ltphp echo $model-gtcommentCount rsquocomment(s)rsquo gt

lth3gt

ltphp $this-gtrenderPartial(rsquo commentsrsquoarray(

rsquopostrsquo=gt$model

rsquocommentsrsquo=gt$model-gtcomments

)) gt

ltphp endif gt

ltdivgt

In the above we call renderPartial() to render a partial view named comments to displaythe list of comments belonging to the current post Note that in the view we use theexpression $model-gtcomments to retrieve the comments for the post This is valid becausewe have declared a comments relation in the Post class Evaluating this expression wouldtrigger an implicit JOIN database query to bring back the proper comments This featureis known as lazy relational query

The partial view comments is not very interesting It mainly goes through every commentand displays the detail of it Interested readers may refer to wwwrootyiidemosblog

protectedpost commentsphp

422 Creating Comments

To handle comment creation we first modify the actionView() method of PostController

as follows

public function actionView()

$post=$this-gtloadModel()

$comment=$this-gtnewComment($post)

$this-gtrender(rsquoviewrsquoarray(

rsquomodelrsquo=gt$post

rsquocommentrsquo=gt$comment

))

36 4 Comment Management

protected function newComment($post)

$comment=new Comment

if(isset($ POST[rsquoCommentrsquo]))

$comment-gtattributes=$ POST[rsquoCommentrsquo]

if($post-gtaddComment($comment))

if($comment-gtstatus==CommentSTATUS PENDING)

Yiiapp()-gtuser-gtsetFlash(rsquocommentSubmittedrsquorsquoThank yoursquo)

$this-gtrefresh()

return $comment

In the above we call the newComment() method before we render view In the newComment()

method we generate a Comment instance and check if the comment form is submitted If sowe try to add the comment for the post by calling $post-gtaddComment($comment) If it goesthrough we refresh the post detail page In case the comment needs to be approved we willshow a flash message to indicate this decision A flash message is usually a confirmationmessage displayed to end users If the user clicks on the refresh button of his browser themessage will disappear

We also need to modify wwwrootblogprotectedviewspostviewphp furthermore

ltdiv id=commentsgt

lth3gtLeave a Commentlth3gt

ltphp if(Yiiapp()-gtuser-gthasFlash(rsquocommentSubmittedrsquo)) gt

ltdiv class=successgt

ltphp echo Yiiapp()-gtuser-gtgetFlash(rsquocommentSubmittedrsquo) gt

ltdivgt

ltphp else gt

ltphp $this-gtrenderPartial(rsquocomment formrsquoarray(

rsquomodelrsquo=gt$comment

)) gt

ltphp endif gt

ltdivgtlt-- comments --gt

43 Managing Comments 37

In the above code we display the flash message if it is available If not we displaythe comment input form by rendering the partial view wwwrootblogprotectedviews

comment formphp

43 Managing Comments

Comment management includes updating deleting and approving comments These op-erations are implemented as actions in the CommentController class

431 Updating and Deleting Comments

The code generated by yiic for updating and deleting comments remains largely un-changed

432 Approving Comments

When comments are newly created they are in pending approval status and need to beapproved in order to be visible to guest users Approving a comment is mainly aboutchanging the status column of the comment

We create an actionApprove() method in CommentController as follows

public function actionApprove()

if(Yiiapp()-gtrequest-gtisPostRequest)

$comment=$this-gtloadModel()

$comment-gtapprove()

$this-gtredirect(array(rsquoindexrsquo))

else

throw new CHttpException(400rsquoInvalid requestrsquo)

In the above when the approve action is invoked via a POST request we call the approve()

method defined in the Comment model to change the status We then redirect the userbrowser to the page displaying the post that this comment belongs to

We also modify the actionIndex() method of Comment to show all comments We wouldlike to see comments pending approved to show up first

public function actionIndex()

38 4 Comment Management

$dataProvider=new CActiveDataProvider(rsquoCommentrsquo array(

rsquocriteriarsquo=gtarray(

rsquowithrsquo=gtrsquopostrsquo

rsquoorderrsquo=gtrsquotstatus tcreate time DESCrsquo

)

))

$this-gtrender(rsquoindexrsquoarray(

rsquodataProviderrsquo=gt$dataProvider

))

Notice that in the above code because both tbl post and tbl comment have columns statusand create time we need to disambiguate the corresponding column reference by prefixingthem with table alias names As described in the guide the alias for the primary table ina relational query is always t Therefore we are prefixing t to the status and create time

columns in the above code

Like the post index view the index view for CommentController uses [CListView] to displaythe comment list which in turn uses the partial view wwwrootblogprotectedviews

comment viewphp to display the detail of each individual comment We will not go intodetails here Interested readers may refer to the corresponding file in the blog demowwwrootyiidemosblogprotectedviewscomment viewphp

Chapter 5

Portlets

51 Customizing Page Layout

Features like rdquothe most recent commentsrdquo rdquotag cloudrdquo are better to be implemented inportlets A portlet is a pluggable user interface component that renders a fragment ofHTML code In this section we describe how to set up the portlet architecture for ourblog application

Before we go into details about developing portlets we first need to adjust our page layoutso that the portlets can be placed in a side bar

A page layout determines how to share common sections (eg header footer) amongdifferent pages and how to embed contents that are specific for different pages In Yiia page layout is represented as a view under the protectedviewslayouts folder Whenwe call the render() method in a controller the rendering result will be automaticallyembedded into this layout view before it is displayed to end users The default layoutview is named as main which means the wwwrootblogprotectedviewslayoutsmain

php file in our blog application

We could directly modify the main layout to add a side bar However doing so wouldmake some irrelevant pages such as about contact pages to have a side bar which is notalways desirable For this reason we need different layouts for different pages

We thus create two different layouts column1 and column2 The former displays the pagein a single column while the latter displays two columns in the page one of which is theside bar We will use column2 layout for the post and comment pages and column1 forthe rest of the pages Because both column1 and column2 still share the same header andfooter sections we let them to share the main layout

Based on the above description we create the column1 layout as follows

ltphp $this-gtbeginContent(rsquolayoutsmainrsquo) gt

ltdiv class=containergt

40 5 Portlets

ltdiv id=contentgt

ltphp echo $content gt

ltdivgtlt-- content --gt

ltdivgt

ltphp $this-gtendContent() gt

and the column2 layout as follows

ltphp $this-gtbeginContent(rsquolayoutsmainrsquo) gt

ltdiv class=containergt

ltdiv class=span-18gt

ltdiv id=contentgt

ltphp echo $content gt

ltdivgtlt-- content --gt

ltdivgt

ltdiv class=span-6 lastgt

ltdiv id=sidebargt

portlets to be placed here

ltdivgtlt-- sidebar --gt

ltdivgt

ltdivgt

ltphp $this-gtendContent() gt

As we can see both column1 and column2 are enclosed in a pair of beginContent andendContent calls They essentially use the specified view layoutsmain to decorate theenclosed content In all these layout views the variable $content refers to the dynamiccontent to be embedded into the layout

Besides customizing the layout view file we also need to adjust the CSS file wwwroot

blogcssmaincss so that the overall appearance would look like what we see in the blogdemo We will not go into details here

Info The skeleton application generated by the yiic tool uses the BluePrint CSSframework It provides a nice font reset and a robust grid system For more detailson how to use this framework please refer to its documentation

After creating the new layouts we need to set the layout property of controllers to makeuse of them So far we have three controllers SiteController PostController andCommentController We set SiteController to use column1 as the default layout whilethe rest two of the controllers use column2 for example

class PostController extends Controller

52 Creating User Menu Portlet 41

public $layout=rsquocolumn2rsquo

Tip If some view of the controller does not use the same layout as declaredby the layout property it may customize it in the view by a simple assignment$this-gtlayout=rsquonewlayoutrsquo

52 Creating User Menu Portlet

Based on the requirements analysis we need three different portlets the rdquouser menurdquoportlet the rdquotag cloudrdquo portlet and the rdquorecent commentsrdquo portlet We will develop theseportlets by extending the [CPortlet] widget provided by Yii

In this section we will develop our first concrete portlet - the user menu portlet whichdisplays a list of menu items that are only available to authenticated users The menucontains four items

bull Approve Comments a hyperlink that leads to a list of comments pending approval

bull Create New Post a hyperlink that leads to the post creation page

bull Manage Posts a hyperlink that leads to the post management page

bull Logout a link button that would log out the current user

521 Creating UserMenu Class

We create the UserMenu class to represent the logic part of the user menu portlet Theclass is saved in the file wwwrootblogprotectedcomponentsUserMenuphp which has thefollowing content

Yiiimport(rsquoziiwidgetsCPortletrsquo)

class UserMenu extends CPortlet

public function init()

$this-gttitle=CHtmlencode(Yiiapp()-gtuser-gtname)

parentinit()

42 5 Portlets

protected function renderContent()

$this-gtrender(rsquouserMenursquo)

The UserMenu class extends from the CPortlet class that we created previously It overridesboth the init() method and the renderContent() method of CPortlet The former setsthe portlet title to be the name of the current user the latter generates the portlet bodycontent by rendering a view named userMenu

Tip Notice that we have to explicitly include the CPortlet class by calling Yii

import() before we refer to it the first time This is because CPortlet is part of thezii project ndash the official extension library for Yii For performance considerationclasses in this project are not listed as core classes Therefore we have to import itbefore we use it the first time

522 Creating userMenu View

Next we create the userMenu view which is saved in the file wwwrootblogprotected

componentsviewsuserMenuphp

ltulgt

ltligtltphp echo CHtmllink(rsquoCreate New Postrsquoarray(rsquopostcreatersquo)) gtltligt

ltligtltphp echo CHtmllink(rsquoManage Postsrsquoarray(rsquopostadminrsquo)) gtltligt

ltligtltphp echo CHtmllink(rsquoApprove Commentsrsquoarray(rsquocommentindexrsquo))

rsquo (rsquo Commentmodel()-gtpendingCommentCount rsquo)rsquo gtltligt

ltligtltphp echo CHtmllink(rsquoLogoutrsquoarray(rsquositelogoutrsquo)) gtltligt

ltulgt

Info By default view files for a widget should be placed under the views sub-directory of the directory containing the widget class file The file name must bethe same as the view name

523 Using UserMenu Portlet

It is time for us to make use of our newly completed UserMenu portlet We modify thelayout view file wwwrootblogprotectedviewslayoutscolumn2php as follows

53 Creating Tag Cloud Portlet 43

ltdiv id=sidebargt

ltphp if(Yiiapp()-gtuser-gtisGuest) $this-gtwidget(rsquoUserMenursquo) gt

ltdivgt

In the above we call the widget() method to generate and execute an instance of theUserMenu class Because the portlet should only be displayed to authenticated users weonly call widget() when the isGuest property of the current user is false (meaning theuser is authenticated)

524 Testing UserMenu Portlet

Letrsquos test what we have so far

1 Open a browser window and enter the URL httpwwwexamplecomblogindex

php Verify that there is nothing displayed in the side bar section of the page

2 Click on the Login hyperlink and fill out the login form to login If successful verifythat the UserMenu portlet appears in the side bar and the portlet has the usernameas its title

3 Click on the rsquoLogoutrsquo hyperlink in the UserMenu portlet Verify that the logout actionis successful and the UserMenu portlet disappears

525 Summary

What we have created is a portlet that is highly reusable We can easily reuse it in adifferent project with little or no modification Moreover the design of this portlet followsclosely the philosophy that logic and presentation should be separated While we did notpoint this out in the previous sections such practice is used nearly everywhere in a typicalYii application

53 Creating Tag Cloud Portlet

Tag cloud displays a list of post tags with visual decorations hinting the popularity of eachindividual tag

531 Creating TagCloud Class

We create the TagCloud class in the file wwwrootblogprotectedcomponentsTagCloud

php The file has the following content

44 5 Portlets

Yiiimport(rsquoziiwidgetsCPortletrsquo)

class TagCloud extends CPortlet

public $title=rsquoTagsrsquo

public $maxTags=20

protected function renderContent()

$tags=Tagmodel()-gtfindTagWeights($this-gtmaxTags)

foreach($tags as $tag=gt$weight)

$link=CHtmllink(CHtmlencode($tag) array(rsquopostindexrsquorsquotagrsquo=gt$tag))

echo CHtmltag(rsquospanrsquo array(

rsquoclassrsquo=gtrsquotagrsquo

rsquostylersquo=gtfont-size$weightpt) $link)n

Unlike the UserMenu portlet the TagCloud portlet does not use a view Instead its presen-tation is done in the renderContent() method This is because the presentation does notcontain much HTML tags

We display each tag as a hyperlink to the post index page with the corresponding tagparameter The font size of each tag link is adjusted according to their relative weightamong other tags If a tag has higher frequency value than the other it will have a biggerfont size

532 Using TagCloud Portlet

Usage of the TagCloud portlet is very simple We modify the layout file wwwrootblog

protectedviewslayoutsmainphp as follows

ltdiv id=sidebargt

ltphp if(Yiiapp()-gtuser-gtisGuest) $this-gtwidget(rsquoUserMenursquo) gt

ltphp $this-gtwidget(rsquoTagCloudrsquo array(

rsquomaxTagsrsquo=gtYiiapp()-gtparams[rsquotagCloudCountrsquo]

)) gt

ltdivgt

54 Creating Recent Comments Portlet 45

54 Creating Recent Comments Portlet

In this section we create the last portlet that displays a list of comments recently pub-lished

541 Creating RecentComments Class

We create the RecentComments class in the file wwwrootblogprotectedcomponentsRecentCommentsphp The file has the following content

Yiiimport(rsquoziiwidgetsCPortletrsquo)

class RecentComments extends CPortlet

public $title=rsquoRecent Commentsrsquo

public $maxComments=10

public function getRecentComments()

return Commentmodel()-gtfindRecentComments($this-gtmaxComments)

protected function renderContent()

$this-gtrender(rsquorecentCommentsrsquo)

In the above we invoke the findRecentComments method which is defined in the Comment

class as follows

class Comment extends CActiveRecord

public function findRecentComments($limit=10)

return $this-gtwith(rsquopostrsquo)-gtfindAll(array(

rsquoconditionrsquo=gtrsquotstatus=rsquoselfSTATUS APPROVED

rsquoorderrsquo=gtrsquotcreate time DESCrsquo

rsquolimitrsquo=gt$limit

))

46 5 Portlets

542 Creating recentComments View

The recentComments view is saved in the file wwwrootblogprotectedcomponentsviews

recentCommentsphp It simply displays every comment returned by the RecentComments

getRecentComments() method

543 Using RecentComments Portlet

We modify the layout file wwwrootblogprotectedviewslayoutsmainphp to embedthis last portlet

ltdiv id=sidebargt

ltphp if(Yiiapp()-gtuser-gtisGuest) $this-gtwidget(rsquoUserMenursquo) gt

ltphp $this-gtwidget(rsquoTagCloudrsquo array(

rsquomaxTagsrsquo=gtYiiapp()-gtparams[rsquotagCloudCountrsquo]

)) gt

ltphp $this-gtwidget(rsquoRecentCommentsrsquo array(

rsquomaxCommentsrsquo=gtYiiapp()-gtparams[rsquorecentCommentCountrsquo]

)) gt

ltdivgt

Chapter 6

Final Work

61 Beautifying URLs

The URLs linking various pages of our blog application currently look ugly For examplethe URL for the page showing a post looks like the following

indexphpr=postshowampid=1

In this section we describe how to beautify these URLs and make them SEO-friendlyOur goal is to be able to use the following URLs in the application

1 indexphppostsyii leads to the page showing a list of posts with tag yii

2 indexphppost2A+Test+Post leads to the page showing the detail of the postwith ID 2 whose title is A Test Post

3 indexphppostupdate1 leads to the page that allows updating the post with ID1

Note that in the second URL format we include the post title in the URL This is mainlyto make the URL SEO friendly It is said that search engines may also respect the wordsfound in a URL when it is being indexed

To achieve our goal we modify the application configuration as follows

return array(

rsquocomponentsrsquo=gtarray(

rsquourlManagerrsquo=gtarray(

rsquourlFormatrsquo=gtrsquopathrsquo

rsquorulesrsquo=gtarray(

48 6 Final Work

rsquopostltidd+gtlttitlegtrsquo=gtrsquopostviewrsquorsquopostslttaggtrsquo=gtrsquopostindexrsquo

)

)

)

)

In the above we configure the urlManager component by setting its urlFormat propertyto be path and adding a set of rules

The rules are used by urlManager to parse and create the URLs in the desired formatFor example the first rule says that if a URL indexphppostsyii is requested theurlManager component should be responsible to dispatch the request to the route post

index and generate a tag GET parameter with the value yii On the other hand whencreating a URL with the route postindex and parameter tag the urlManager componentwill also use this rule to generate the desired URL indexphppostsyii For this reasonwe say that urlManager is a two-way URL manager

The urlManager component can further beautify our URLs such as hiding indexphp inthe URLs appending suffix like html to the URLs We can obtain these features easilyby configuring various properties of urlManager in the application configuration For moredetails please refer to the Guide

62 Logging Errors

A production Web application often needs sophisticated logging for various events In ourblog application we would like to log the errors occurring when it is being used Sucherrors could be programming mistakes or usersrsquo misuse of the system Logging these errorswill help us to improve the blog application

We enable the error logging by modifying the application configuration as follows

return array(

rsquopreloadrsquo=gtarray(rsquologrsquo)

rsquocomponentsrsquo=gtarray(

rsquologrsquo=gtarray(

rsquoclassrsquo=gtrsquoCLogRouterrsquo

rsquoroutesrsquo=gtarray(

array(

rsquoclassrsquo=gtrsquoCFileLogRoutersquo

rsquolevelsrsquo=gtrsquoerror warningrsquo

63 Final Tune-up and Deployment 49

)

)

)

)

)

With the above configuration if an error or warning occurs detailed information will belogged and saved in a file located under the directory wwwrootblogprotectedruntime

The log component offers more advanced features such as sending log messages to a listof email addresses displaying log messages in JavaScript console window etc For moredetails please refer to the Guide

63 Final Tune-up and Deployment

We are close to finish our blog application Before deployment we would like to do sometune-ups

631 Changing Home Page

We change to use the post list page as the home page We modify the application config-uration as follows

return array(

rsquodefaultControllerrsquo=gtrsquopostrsquo

)

Tip Because PostController already declares list to be its default action whenwe access the home page of the application we will see the result generated by thelist action of the post controller

632 Enabling Schema Caching

Because ActiveRecord relies on the metadata about tables to determine the column infor-mation it takes time to read the metadata and analyze it This may not be a problemduring development stage but for an application running in production mode it is a totalwaste of time if the database schema does not change Therefore we should enable theschema caching by modifying the application configuration as follows

50 6 Final Work

return array(

rsquocomponentsrsquo=gtarray(

rsquocachersquo=gtarray(

rsquoclassrsquo=gtrsquoCDbCachersquo

)

rsquodbrsquo=gtarray(

rsquoclassrsquo=gtrsquosystemdbCDbConnectionrsquo

rsquoconnectionStringrsquo=gtrsquosqlitewwwrootblogprotecteddatablogdbrsquo

rsquoschemaCachingDurationrsquo=gt3600

)

)

)

In the above we first add a cache component which uses a default SQLite database as thecaching storage If our server is equipped with other caching extensions such as APCwe could change to use them as well We also modify the db component by setting itsschemaCachingDuration property to be 3600 which means the parsed database schemadata can remain valid in cache for 3600 seconds

633 Disabling Debugging Mode

We modify the entry script file wwwrootblogindexphp by removing the line definingthe constant YII DEBUG This constant is useful during development stage because it allowsYii to display more debugging information when an error occurs However when theapplication is running in production mode displaying debugging information is not agood idea because it may contain sensitive information such as where the script file islocated and the content in the file etc

634 Deploying the Application

The final deployment process manly involves copying the directory wwwrootblog to thetarget directory The following checklist shows every needed step

1 Install Yii in the target place if it is not available

2 Copy the entire directory wwwrootblog to the target place

3 Edit the entry script file indexphp by pointing the $yii variable to the new Yiibootstrap file

4 Edit the file protectedyiicphp by setting the $yiic variable to be the new Yiiyiicphp file

64 Future Enhancements 51

5 Change the permission of the directories assets and protectedruntime so that theyare writable by the Web server process

64 Future Enhancements

641 Using a Theme

Without writing any code our blog application is already themeable To use a themewe mainly need to develop the theme by writing customized view files in the theme Forexample to use a theme named classic that uses a different page layout we would createa layout view file wwwrootblogthemesclassicviewslayoutsmainphp We also needto change the application configuration to indicate our choice of the classic theme

return array(

rsquothemersquo=gtrsquoclassicrsquo

)

642 Internationalization

We may also internationalize our blog application so that its pages can be displayed indifferent languages This mainly involves efforts in two aspects

First we may create view files in different languages For example for the list pageof PostController we can create a view file wwwrootblogprotectedviewspostzh cn

listphp When the application is configured to use simplified Chinese (the language codeis zh cn) Yii will automatically use this new view file instead of the original one

Second we may create message translations for those messages generated by code Themessage translations should be saved as files under the directory wwwrootblogprotected

messages We also need to modify the code where we use text strings by enclosing themin the method call Yiit()

For more details about internationalization please refer to the Guide

643 Improving Performance with Cache

While the Yii framework itself is very efficient it is not necessarily true that an applicationwritten in Yii efficient There are several places in our blog application that we can improvethe performance For example the tag clould portlet could be one of the performancebottlenecks because it involves complex database query and PHP logic

52 6 Final Work

We can make use of the sophisticated caching feature provided by Yii to improve theperformance One of the most useful components in Yii is COutputCache which caches afragment of page display so that the underlying code generating the fragment does not needto be executed for every request For example in the layout file wwwrootblogprotected

viewslayoutsmainphp we can enclose the tag cloud portlet with COutputCache

ltphp if($this-gtbeginCache(rsquotagCloudrsquo array(rsquodurationrsquo=gt3600))) gt

ltphp $this-gtwidget(rsquoTagCloudrsquo array(

rsquomaxTagsrsquo=gtYiiapp()-gtparams[rsquotagCloudCountrsquo]

)) gt

ltphp $this-gtendCache() gt

With the above code the tag cloud display will be served from cache instead of beinggenerated on-the-fly for every request The cached content will remain valid in cache for3600 seconds

644 Adding New Features

Our blog application only has very basic functionalities To become a complete blogsystem more features are needed for example calendar portlet email notifications postcategorization archived post portlet and so on We will leave the implementation of thesefeatures to interested readers

  • Contents
  • License
  • Getting Started
    • Building a Blog System using Yii
    • Testdriving with Yii
      • Installing Yii
      • Creating Skeleton Application
      • Application Workflow
        • Requirements Analysis
        • Overall Design
          • Initial Prototyping
            • Setting Up Database
              • Creating Database
              • Establishing Database Connection
                • Scaffolding
                • Authenticating User
                • Summary
                  • Post Management
                    • Customizing Post Model
                      • Customizing rules() Method
                      • Customizing relations() Method
                      • Representing Status in Text
                        • Creating and Updating Posts
                          • Customizing Access Control
                          • Customizing create and update Operations
                            • Displaying Posts
                              • Customizing view Operation
                              • Customizing index Operation
                                • Managing Posts
                                  • Listing Posts in Tabular View
                                  • Deleting Posts
                                      • Comment Management
                                        • Customizing Comment Model
                                          • Customizing rules() Method
                                          • Customizing attributeLabels() Method
                                          • Customizing Saving Process
                                            • Creating and Displaying Comments
                                              • Displaying Comments
                                              • Creating Comments
                                                • Managing Comments
                                                  • Updating and Deleting Comments
                                                  • Approving Comments
                                                      • Portlets
                                                        • Customizing Page Layout
                                                        • Creating User Menu Portlet
                                                          • Creating UserMenu Class
                                                          • Creating userMenu View
                                                          • Using UserMenu Portlet
                                                          • Testing UserMenu Portlet
                                                          • Summary
                                                            • Creating Tag Cloud Portlet
                                                              • Creating TagCloud Class
                                                              • Using TagCloud Portlet
                                                                • Creating Recent Comments Portlet
                                                                  • Creating RecentComments Class
                                                                  • Creating recentComments View
                                                                  • Using RecentComments Portlet
                                                                      • Final Work
                                                                        • Beautifying URLs
                                                                        • Logging Errors
                                                                        • Final Tune-up and Deployment
                                                                          • Changing Home Page
                                                                          • Enabling Schema Caching
                                                                          • Disabling Debugging Mode
                                                                          • Deploying the Application
                                                                            • Future Enhancements
                                                                              • Using a Theme
                                                                              • Internationalization
                                                                              • Improving Performance with Cache
                                                                              • Adding New Features
Page 20: Yii Blog 1.1.0 Demo Tutor

22 Scaffolding 13

bull model files

ndash modelsUserphp contains the User class that extends from CActiveRecord andcan be used to access the tbl user database table

ndash modelsPostphp contains the Post class that extends from CActiveRecord andcan be used to access the tbl post database table

ndash modelsTagphp contains the Tag class that extends from CActiveRecord andcan be used to access the tbl tag database table

ndash modelsCommentphp contains the Comment class that extends from CActiveRe-cord and can be used to access the tbl comment database table

ndash modelsLookupphp contains the Lookup class that extends from CActiveRecordand can be used to access the tbl lookup database table

bull controller file

ndash controllersPostControllerphp contains the PostController class which is thecontroller in charge of all CRUD operations about posts

ndash controllersCommentControllerphp contains the CommentController class whichis the controller in charge of all CRUD operations about comments

bull view files

ndash viewspostcreatephp is the view file that shows an HTML form to create anew post

ndash viewspostupdatephp is the view file that shows an HTML form to update anexisting post

ndash viewspostviewphp is the view file that displays the detailed information of apost

ndash viewspostindexphp is the view file that displays a list of posts

ndash viewspostadminphp is the view file that displays posts in a table with ad-ministrative commands

ndash viewspost formphp is the partial view file embedded in viewspostcreate

php and viewspostupdatephp It displays the HTML form for collecting postinformation

ndash viewspost viewphp is the partial view file used by viewspostindexphp Itdisplays the brief view of a single post

ndash a similar set of view files are also generated for comment

14 2 Initial Prototyping

In order to understand better how the above files are used we show in the following theworkflow that occurs in the blog application when displaying a list of posts

1 The user requests the URL httpwwwexamplecomblogindexphpr=posts

2 The entry script is executed by the Web server which creates and initializes anapplication instance to handle the request

3 The application creates an instance of PostController and executes it

4 The PostController instance executes the index action by calling its actionIndex()

method Note that index is the default action if the user does not specify an actionto execute in the URL

5 The actionIndex() method queries database to bring back the list of recent posts

6 The actionIndex() method renders the index view with the post data

23 Authenticating User

Our blog application needs to differentiate between the system owner and guest usersTherefore we need to implement the user authentication feature

As you may have found that the skeleton application already provides user authenticationby checking if the username and password are both demo or admin In this section wewill modify the corresponding code so that the authentication is done against the User

database table

User authentication is performed in a class implementing the IUserIdentity interface Theskeleton application uses the UserIdentity class for this purpose The class is stored inthe file wwwrootblogprotectedcomponentsUserIdentityphp

Tip By convention the name of a class file must be the same as the correspondingclass name suffixed with the extension php Following this convention one canrefer to a class using a path alias For example we can refer to the UserIdentity

class with the alias applicationcomponentsUserIdentity Many APIs in Yiican recognize path aliases (eg YiicreateComponent()) and using path aliasesavoids the necessity of embedding absolute file paths in the code The existence ofthe latter often causes trouble when we deploy an application

We modify the UserIdentity class as follows

23 Authenticating User 15

ltphp

class UserIdentity extends CUserIdentity

private $ id

public function authenticate()

$username=strtolower($this-gtusername)

$user=Usermodel()-gtfind(rsquoLOWER(username)=rsquoarray($username))

if($user===null)

$this-gterrorCode=selfERROR USERNAME INVALID

else if($user-gtvalidatePassword($this-gtpassword))

$this-gterrorCode=selfERROR PASSWORD INVALID

else

$this-gt id=$user-gtid

$this-gtusername=$user-gtusername

$this-gterrorCode=selfERROR NONE

return $this-gterrorCode==selfERROR NONE

public function getId()

return $this-gt id

In the authenticate() method we use the User class to look for a row in the User tablewhose username column is the same as the given username in a case-insensitive mannerRemember that the User class was created using the yiic tool in the prior section Becausethe User class extends from CActiveRecord we can exploit the ActiveRecord feature toaccess the User table in an OOP fashion

In order to check if the user has entered a valid password we invoke the validatePassword

method of the User class We need to modify the file wwwrootblogprotectedmodels

Userphp as follows Note that instead of storing the plain password in the database westore the hash result of the password and a randomly generated salt key When validatingthe user-entered password we should compare the hash results instead

class User extends CActiveRecord

public function validatePassword($password)

return $this-gthashPassword($password$this-gtsalt)===$this-gtpassword

16 2 Initial Prototyping

public function hashPassword($password$salt)

return md5($salt$password)

In the UserIdentity class we also override the getId() method which returns the id valueof the user found in the User table The parent implementation would return the usernameinstead Both the username and id properties will be stored in the user session and maybe accessed via Yiiapp()-gtuser from anywhere in our code

Tip In the UserIdentity class we reference the class CUserIdentity without ex-plicitly including the corresponding class file This is because CUserIdentity is acore class provided by the Yii framework Yii will automatically include the classfile for any core class when it is referenced for the first time

We also do the same with the User class This is because the User class file is placedunder the directory wwwrootblogprotectedmodels which has been added tothe PHP include path according to the following lines found in the applicationconfiguration

return array(

rsquoimportrsquo=gtarray(

rsquoapplicationmodelsrsquo

rsquoapplicationcomponentsrsquo

)

)

The above configuration says that any class whose class file is located under either wwwrootblogprotectedmodels or wwwrootblogprotectedcomponents willbe automatically included when the class is referenced for the first time

The UserIdentity class is mainly used by the LoginForm class to authenticate a user basedon the username and password input collected from the login page The following codefragment shows how UserIdentity is used

$identity=new UserIdentity($username$password)

$identity-gtauthenticate()

switch($identity-gterrorCode)

24 Summary 17

case UserIdentityERROR NONE

Yiiapp()-gtuser-gtlogin($identity)

break

Info People often get confused about identity and the user application componentThe former represents a way of performing authentication while the latter is usedto represent the information related with the current user An application can onlyhave one user component but it can have one or several identity classes dependingon what kind of authentication it supports Once authenticated an identity instancemay pass its state information to the user component so that they are globallyaccessible via user

To test the modified UserIdentity class we can browse the URL httpwwwexample

comblogindexphp and try logging in with the username and password that we store inthe User table If we use the database provided by the blog demo we should be able tologin with username demo and password demo Note that this blog system does not providethe user management feature As a result a user cannot change his account or create anew one through the Web interface The user management feature may be considered asa future enhancement to the blog application

24 Summary

We have completed the milestone 1 Letrsquos summarize what we have done so far

1 We identified the requirements to be fulfilled

2 We installed the Yii framework

3 We created a skeleton application

4 We designed and created the blog database

5 We modified the application configuration by adding the database connection

6 We generated the code that implements the basic CRUD operations for both postsand comments

7 We modified the authentication method to check against the tbl user table

18 2 Initial Prototyping

For a new project most of the time will be spent in step 1 and 4 for this first milestone

Although the code generated by the yiic tool implements fully functional CRUD oper-ations for a database table it often needs to be modified in practical applications Forthis reason in the next two milestone our job is to customize the generated CRUD codeabout posts and comments so that it reaches our initial requirements

In general we first modify the model class file by adding appropriate validation rules anddeclaring relational objects We then modify the controller action and view code for eachindividual CRUD operation

Chapter 3

Post Management

31 Customizing Post Model

The Post model class generated by the yiic tool mainly needs to be modified in two places

bull the rules() method specifies the validation rules for the model attributes

bull the relations() method specifies the related objects

Info A model consists of a list of attributes each associated with a column in thecorresponding database table Attributes can be declared explicitly as class membervariables or implicitly without any declaration

311 Customizing rules() Method

We first specify the validation rules which ensure the attribute values entered by usersare correct before they are saved to the database For example the status attribute ofPost should be an integer 1 2 or 3 The yiic tool also generates validation rules for eachmodel However these rules are based on the table column information and may not beappropriate

Based on the requirement analysis we modify the rules() method as follows

public function rules()

return array(

array(rsquotitle content statusrsquo rsquorequiredrsquo)

array(rsquotitlersquo rsquolengthrsquo rsquomaxrsquo=gt128)

array(rsquostatusrsquo rsquoinrsquo rsquorangersquo=gtarray(123))

array(rsquotagsrsquo rsquomatchrsquo rsquopatternrsquo=gtrsquo^[ws]+$rsquorsquomessagersquo=gtrsquoTags can only contain word charactersrsquo)

20 3 Post Management

array(rsquotagsrsquo rsquonormalizeTagsrsquo)

)

In the above we specify that the title content and status attributes are required thelength of title should not exceed 128 the status attribute value should be 1 (draft) 2(published) or 3 (archived) and the tags attribute should only contain word charactersand commas In addition we use normalizeTags to normalize the user-entered tags so thatthe tags are unique and properly separated with commas

The validators such as required length in and match are all built-in validators providedby Yii The normalizeTags validator is a method-based validator that we need to definein the Post class For more information about how to specify validation rules please referto the Guide

public function normalizeTags($attribute$params)

$this-gttags=Tagarray2string(array unique(Tagstring2array($this-gttags)))

The rules declared in this method are executed one by one when we call the validate() orsave() method of the model instance

Note It is very important to remember that attributes appearing in rules() mustbe those to be entered by end users Other attributes such as id and create time

in the Post model which are set by our code or database should not be in rules()For more details please refer to Securing Attribute Assignments

After making these changes we can visit the post creation page again to verify that thenew validation rules are taking effect

312 Customizing relations() Method

Lastly we customize the relations() method to specify the related objects of a post Bydeclaring these related objects in relations() we can exploit the powerful RelationalActiveRecord (RAR) feature to access the related object information of a post such asits author and comments without the need to write complex SQL JOIN statements

We customize the relations() method as follows

31 Customizing Post Model 21

public function relations()

return array(

rsquoauthorrsquo =gt array(selfBELONGS TO rsquoUserrsquo rsquoauthor idrsquo)

rsquocommentsrsquo =gt array(selfHAS MANY rsquoCommentrsquo rsquopost idrsquo

rsquoconditionrsquo=gtrsquocommentsstatus=rsquoCommentSTATUS APPROVED

rsquoorderrsquo=gtrsquocommentscreate time DESCrsquo)

rsquocommentCountrsquo =gt array(selfSTAT rsquoCommentrsquo rsquopost idrsquo

rsquoconditionrsquo=gtrsquostatus=rsquoCommentSTATUS APPROVED)

)

The above relations state that

bull A post belongs to an author whose class is User and the relationship is establishedbased on the author id attribute value of the post

bull A post has many comments whose class is Comment and the relationship is establishedbased on the post id attribute value of the comments These comments should besorted according to their creation time and the comments must be approved

bull The commentCount relation is a bit special as it returns back an aggregation resultwhich is about how many comments the post has

With the above relation declaration we can easily access the author and comments of apost like the following

$author=$post-gtauthor

echo $author-gtusername

$comments=$post-gtcomments

foreach($comments as $comment)

echo $comment-gtcontent

For more details about how to declare and use relations please refer to the Guide

313 Representing Status in Text

Because the status of a post is stored as an integer in the database we need to provide atextual representation so that it is more intuitive when being displayed to end users In alarge system the similar requirement is very common

22 3 Post Management

As a generic solution we use the tbl lookup table to store the mapping between integervalues and textual representations that are needed by other data objects We modify theLookup model class as follows to more easily access the textual data in the table

class Lookup extends CActiveRecord

private static $ items=array()

public static function items($type)

if(isset(self$ items[$type]))

selfloadItems($type)

return self$ items[$type]

public static function item($type$code)

if(isset(self$ items[$type]))

selfloadItems($type)

return isset(self$ items[$type][$code]) self$ items[$type][$code] false

private static function loadItems($type)

self$ items[$type]=array()

$models=selfmodel()-gtfindAll(array(

rsquoconditionrsquo=gtrsquotype=typersquo

rsquoparamsrsquo=gtarray(rsquotypersquo=gt$type)

rsquoorderrsquo=gtrsquopositionrsquo

))

foreach($models as $model)

self$ items[$type][$model-gtcode]=$model-gtname

Our new code mainly provides two static methods Lookupitems() and Lookupitem()The former returns a list of strings belonging to the specified data type while the latterreturns a particular string for the given data type and data value

Our blog database is pre-populated with two lookup types PostStatus and CommentStatusThe former refers to the possible post statuses while the latter the comment statuses

In order to make our code easier to read we also declare a set of constants to representthe status integer values We should use these constants through our code when referringto the corresponding status values

32 Creating and Updating Posts 23

class Post extends CActiveRecord

const STATUS DRAFT=0

const STATUS PUBLISHED=1

const STATUS ARCHIVED=2

Therefore we can call Lookupitems(rsquoPostStatusrsquo) to get the list of possible post statuses(text strings indexed by the corresponding integer values) and call Lookupitem(rsquoPostStatusrsquoPostSTATUS PUBLISHED) to get the string representation of the published status

32 Creating and Updating Posts

With the Post model ready we need to fine-tune the actions and views for the controllerPostController In this section we first customize the access control of CRUD operationswe then modify the code implementing the create and update operations

321 Customizing Access Control

The first thing we want to do is to customize the access control because the code generatedby yiic does not fit our needs

We modify the accessRules() method in the file wwwrootblogprotectedcontrollers

PostControllerphp as follows

public function accessRules()

return array(

array(rsquoallowrsquo allow all users to perform rsquolistrsquo and rsquoshowrsquo actions

rsquoactionsrsquo=gtarray(rsquoindexrsquo rsquoviewrsquo)

rsquousersrsquo=gtarray(rsquorsquo)

)

array(rsquoallowrsquo allow authenticated users to perform any action

rsquousersrsquo=gtarray(rsquorsquo)

)

array(rsquodenyrsquo deny all users

rsquousersrsquo=gtarray(rsquorsquo)

)

)

The above rules state that all users can access the index and view actions and authenti-cated users can access any actions including the admin action The user should be denied

24 3 Post Management

access in any other scenario Note that these rules are evaluated in the order they arelisted here The first rule matching the current context makes the access decision Forexample if the current user is the system owner who tries to visit the post creation pagethe second rule will match and it will give the access to the user

322 Customizing create and update Operations

The create and update operations are very similar They both need to display an HTMLform to collect user inputs validate them and save them into database The main differ-ence is that the update operation will pre-populate the form with the existing post datafound in the database For this reason the yiic tool generates a partial view wwwroot

blogprotectedviewspost formphp that is embedded in both the create and update

views to render the needed HTML form

We first change the formphp file so that the HTML form only collects the inputs wewant title content tags and status We use plain text fields to collect inputs for thefirst three attributes and a dropdown list to collect input for status The dropdown listoptions are the text displays of the possible post statuses

ltphp echo CHtmlactiveDropDownList($postrsquostatusrsquoLookupitems(rsquoPostStatusrsquo)) gt

In the above we call Lookupitems(rsquoPostStatusrsquo) to bring back the list of post statuses

We then modify the Post class so that it can automatically set some attributes (eg create

time author id) before a post is saved to the database We override the beforeSave()

method as follows

protected function beforeSave()

if(parentbeforeSave())

if($this-gtisNewRecord)

$this-gtcreate time=$this-gtupdate time=time()

$this-gtauthor id=Yiiapp()-gtuser-gtid

else

$this-gtupdate time=time()

return true

else

return false

33 Displaying Posts 25

When we save a post we want to update the tbl tag table to reflect the change of tagfrequencies We can do this work in the afterSave() method which is automaticallyinvoked by Yii after a post is successfully saved into the database

protected function afterSave()

parentafterSave()

Tagmodel()-gtupdateFrequency($this-gt oldTags $this-gttags)

private $ oldTags

protected function afterFind()

parentafterFind()

$this-gt oldTags=$this-gttags

In the implementation because we want to detect if the user changes the tags in case he isupdating an existing post we need to know what the old tags are For this reason we alsowrite the afterFind() method to keep the old tags in the variable oldTags The methodafterFind() is invoked automatically by Yii when an AR record is populated with thedata from database

We are not going to give details of the TagupdateFrequency() method here Interestedreaders may refer to the file wwwrootyiidemosblogprotectedmodelsTagphp

33 Displaying Posts

In our blog application a post may be displayed among a list of posts or by itself Theformer is implemented as the index operation while the latter the view operation In thissection we customize both operations to fulfill our initial requirements

331 Customizing view Operation

The view operation is implemented by the actionView() method in PostController Itsdisplay is generated by the view view with the view file wwwrootblogprotectedviews

postviewphp

Below is the relevant code implementing the view operation in PostController

public function actionView()

26 3 Post Management

$this-gtrender(rsquoviewrsquoarray(

$post=$this-gtloadModel()

$this-gtrender(rsquoviewrsquoarray(

rsquomodelrsquo=gt$post

))

))

private $ model

public function loadModel()

if($this-gt model===null)

if(isset($ GET[rsquoidrsquo]))

if(Yiiapp()-gtuser-gtisGuest)

$condition=rsquostatus=rsquoPostSTATUS PUBLISHED

rsquo OR status=rsquoPOSTSTATUS ARCHIVED

else

$condition=rsquorsquo

$this-gt model=Postmodel()-gtfindbyPk($ GET[rsquoidrsquo] $condition)

if($this-gt model===null)

throw new CHttpException(404rsquoThe requested page does not existrsquo)

return $this-gt model

Our change mainly lies in the loadModel() method In this method we query the Post

table according to the id GET parameter If the post is not found or if it is not publishedor archived (when the user is a guest) we will throw a 404 HTTP error Otherwise thepost object is returned to actionView() which in turn passes the post object to the viewscript for further display

Tip Yii captures HTTP exceptions (instances of CHttpException) and displaysthem in either predefined templates or customized error views The skeleton appli-cation generated by yiic already contains a customized error view in wwwroot

blogprotectedviewssiteerrorphp We can modify this file if we want tofurther customize the error display

The change in the view script is mainly about ajdusting the formatting and styles of thepost display We will not go into details here Interested readers may refer to wwwroot

blogprotectedviewspostviewphp

33 Displaying Posts 27

332 Customizing index Operation

Like the view operation we customize the index operation in two places the actionIndex()

method in PostController and the view file wwwrootblogprotectedviewspostindex

php We mainly need to add the support for displaying a list of posts that are associatedwith a specified tag

Below is the modified actionIndex() method in PostController

public function actionIndex()

$criteria=new CDbCriteria(array(

rsquoconditionrsquo=gtrsquostatus=rsquoPostSTATUS PUBLISHED

rsquoorderrsquo=gtrsquoupdate time DESCrsquo

rsquowithrsquo=gtrsquocommentCountrsquo

))

if(isset($ GET[rsquotagrsquo]))

$criteria-gtaddSearchCondition(rsquotagsrsquo$ GET[rsquotagrsquo])

$dataProvider=new CActiveDataProvider(rsquoPostrsquo array(

rsquopaginationrsquo=gtarray(

rsquopageSizersquo=gtYiiapp()-gtparams[rsquopostsPerPagersquo]

)

rsquocriteriarsquo=gt$criteria

))

$this-gtrender(rsquoindexrsquoarray(

rsquodataProviderrsquo=gt$dataProvider

))

In the above we first create a query criteria for retrieving post list The criteria statesthat only published posts should be returned and they should be sorted according to theirupdate time in descending order Because when displaying a post in the list we want toshow how many comments the post has received in the criteria we also specify to bringback commentCount which if you remember is a relation declared in Postrelations()

In case when a user wants to see posts with a specific tag we would add a search conditionto the criteria to look for the specified tag

Using the query criteria we create a data provider which mainly serves for three purposesFirst it does pagination of the data when too many results may be returned Herewe customize the pagination by setting the page size to be an application parameterpostsPerPage Second it does sorting according to the user request And finally it feedsthe paginated and sorted data to widgets or view code for presentation

28 3 Post Management

After we finish with actionIndex() we modify the index view as follows Our change ismainly about adding the h1 header when the user specifies to display posts with a tag

ltphp if(empty($ GET[rsquotagrsquo])) gt

lth1gtPosts Tagged with ltigtltphp echo CHtmlencode($ GET[rsquotagrsquo]) gtltigtlth1gt

ltphp endif gt

ltphp $this-gtwidget(rsquoziiwidgetsCListViewrsquo array(

rsquodataProviderrsquo=gt$dataProvider

rsquoitemViewrsquo=gtrsquo viewrsquo

rsquotemplatersquo=gtitemsnpager)) gt

Note that in the above we use [CListView] to display the post list This widget requires apartial view to display the detail of each individual post Here we specify the partial viewto be view which means the file wwwrootblogprotectedviewspost viewphp In thisview script we can acccess the post instance being displayed via a local variable named$data

34 Managing Posts

Managing posts mainly refers to listing posts in an administrative view that allows us tosee posts with all statuses updating them and deleting them They are accomplished bythe admin operation and the delete operation respectively The code generated by yiic

does not need much modification Below we mainly explain how these two operations areimplemented

341 Listing Posts in Tabular View

The admin operation shows posts with all statuses in a tabular view The view supportssorting and pagination The following is the actionAdmin() method in PostController

public function actionAdmin()

$dataProvider=new CActiveDataProvider(rsquoPostrsquo array(

rsquosortrsquo=gtarray(

rsquodefaultOrderrsquo=gtrsquostatus update time DESCrsquo

)

))

$this-gtrender(rsquoadminrsquoarray(

rsquodataProviderrsquo=gt$dataProvider

))

34 Managing Posts 29

The above code is very similar to that in actionIndex() The main difference is in thequery criteria Here we do not specify any particular query criteria since we want to seeall posts We do customize the sort property of the data provider so that by default theposts are shown in the order of their statuses and update time

Below is the code for the admin view

lth1gtManage Postslth1gt

ltphp $this-gtwidget(rsquoziiwidgetsgridCGridViewrsquo array(

rsquodataProviderrsquo=gt$dataProvider

rsquocolumnsrsquo=gtarray(

array(

rsquonamersquo=gtrsquotitlersquo

rsquotypersquo=gtrsquorawrsquo

rsquovaluersquo=gtrsquoCHtmllink(CHtmlencode($data-gttitle) $data-gturl)rsquo

)

array(

rsquonamersquo=gtrsquostatusrsquo

rsquovaluersquo=gtrsquoLookupitem(PostStatus$data-gtstatus)rsquo

)

rsquocreate timedatetimersquo

rsquoupdate timedatetimersquo

array(

rsquoclassrsquo=gtrsquoCButtonColumnrsquo

)

)

)) gt

We use [CGridView] to display the posts It allows us to sort by a column and paginatethrough the posts if there are too many to be displayed in a single page Our change ismainly about how to display each column For example for the title column we specifythat it should be displayed as a hyperlink that points to the detailed view of the post

Tip When displaying text we call CHtmlencode() to encode HTML entities init This prevents from cross-site scripting attack

342 Deleting Posts

In the admin data grid there is a delete button in each row Clicking on the button shoulddelete the corresponding post Internally this triggers the delete action implemented asfollows

30 3 Post Management

public function actionDelete()

if(Yiiapp()-gtrequest-gtisPostRequest)

we only allow deletion via POST request

$this-gtloadModel()-gtdelete()

if(isset($ POST[rsquoajaxrsquo]))

$this-gtredirect(array(rsquoindexrsquo))

else

throw new CHttpException(400rsquoInvalid request Please do not repeat this request againrsquo)

The above code is the one generated by the yiic tool without any change We wouldlike to explain a little bit more about the checking on $ POST[rsquoajaxrsquo] The [CGridView]widget has a very nice feature that its sorting pagination and deletion operations are alldone in AJAX mode by default That means the whole page does not get reloaded if anyof the above operations is performed However it is also possible that the widget runs innon-AJAX mode (by setting its ajaxUpdate property to be false or disabling JavaScript onthe client side) It is necessary for the delete action to differentiate these two scenarios ifthe delete request is made via AJAX we should not redirect the user browser otherwisewe should

Deleting a post should also cause the deletion of all comments for that post In additionwe should also update the tbl tag table regarding the tags for the deleted post Both ofthese tasks can be achieved by writing an afterDelete method in the Post model class asfollows

protected function afterDelete()

parentafterDelete()

Commentmodel()-gtdeleteAll(rsquopost id=rsquo$this-gtid)

Tagmodel()-gtupdateFrequency($this-gttags rsquorsquo)

The above code is very straightforward it first deletes all those comments whose post id

is the same as the ID of the deleted post it then updates the tbl tag table for the tags

of the deleted post

34 Managing Posts 31

Tip We have to explicitly delete all comments for the deleted post here becauseSQLite does not really support foreign key constraints In a DBMS that supportsthis constraint (such as MySQL PostgreSQL) the foreign key constraint can be setup such that the DBMS automatically deletes the related comments if the post isdeleted In that case we no longer this explicit deletion call in our code

32 3 Post Management

Chapter 4

Comment Management

41 Customizing Comment Model

For the Comment model we mainly need to customize the rules() and attributeLabels()

methods The attributeLabels() method returns a mapping between attribute namesand attribute labels We do not need to touch relations() since the code generated bythe yiic tool is good enough

411 Customizing rules() Method

We first customize the validation rules generated by the yiic tool The following rules areused for comments

public function rules()

return array(

array(rsquocontent author emailrsquo rsquorequiredrsquo)

array(rsquoauthor email urlrsquo rsquolengthrsquo rsquomaxrsquo=gt128)

array(rsquoemailrsquorsquoemailrsquo)

array(rsquourlrsquorsquourlrsquo)

)

In the above we specify that the author email and content attributes are required thelength of author email and url cannot exceed 128 the email attribute must be a validemail address and the url attribute must be a valid URL

412 Customizing attributeLabels() Method

We then customize the attributeLabels() method to declare the label display for eachmodel attribute This method returns an array consisting of name-label pairs When wecall CHtmlactiveLabel() to display an attribute label

34 4 Comment Management

public function attributeLabels()

return array(

rsquoidrsquo =gt rsquoIdrsquo

rsquocontentrsquo =gt rsquoCommentrsquo

rsquostatusrsquo =gt rsquoStatusrsquo

rsquocreate timersquo =gt rsquoCreate Timersquo

rsquoauthorrsquo =gt rsquoNamersquo

rsquoemailrsquo =gt rsquoEmailrsquo

rsquourlrsquo =gt rsquoWebsitersquo

rsquopost idrsquo =gt rsquoPostrsquo

)

Tip If the label for an attribute is not declared in attributeLabels() an algo-rithm will be used to generate an appropriate label For example a label VerifyCode will be generated for attributes verify code or verifyCode

413 Customizing Saving Process

Because we want to record the creation time of a comment we override the beforeSave()

method of Comment like we do for the Post model

protected function beforeSave()

if(parentbeforeSave())

if($this-gtisNewRecord)

$this-gtcreate time=time()

return true

else

return false

42 Creating and Displaying Comments

In this section we implement the comment display and creation features

421 Displaying Comments

Instead of displaying and creating comments on individual pages we use the post detailpage (generated by the view action of PostController) Below the post content display

42 Creating and Displaying Comments 35

we display first a list of comments belonging to that post and then a comment creationform

In order to display comments on the post detail page we modify the view script wwwrootblogprotectedviewspostviewphp as follows

ltdiv id=commentsgt

ltphp if($model-gtcommentCountgt=1) gt

lth3gt

ltphp echo $model-gtcommentCount rsquocomment(s)rsquo gt

lth3gt

ltphp $this-gtrenderPartial(rsquo commentsrsquoarray(

rsquopostrsquo=gt$model

rsquocommentsrsquo=gt$model-gtcomments

)) gt

ltphp endif gt

ltdivgt

In the above we call renderPartial() to render a partial view named comments to displaythe list of comments belonging to the current post Note that in the view we use theexpression $model-gtcomments to retrieve the comments for the post This is valid becausewe have declared a comments relation in the Post class Evaluating this expression wouldtrigger an implicit JOIN database query to bring back the proper comments This featureis known as lazy relational query

The partial view comments is not very interesting It mainly goes through every commentand displays the detail of it Interested readers may refer to wwwrootyiidemosblog

protectedpost commentsphp

422 Creating Comments

To handle comment creation we first modify the actionView() method of PostController

as follows

public function actionView()

$post=$this-gtloadModel()

$comment=$this-gtnewComment($post)

$this-gtrender(rsquoviewrsquoarray(

rsquomodelrsquo=gt$post

rsquocommentrsquo=gt$comment

))

36 4 Comment Management

protected function newComment($post)

$comment=new Comment

if(isset($ POST[rsquoCommentrsquo]))

$comment-gtattributes=$ POST[rsquoCommentrsquo]

if($post-gtaddComment($comment))

if($comment-gtstatus==CommentSTATUS PENDING)

Yiiapp()-gtuser-gtsetFlash(rsquocommentSubmittedrsquorsquoThank yoursquo)

$this-gtrefresh()

return $comment

In the above we call the newComment() method before we render view In the newComment()

method we generate a Comment instance and check if the comment form is submitted If sowe try to add the comment for the post by calling $post-gtaddComment($comment) If it goesthrough we refresh the post detail page In case the comment needs to be approved we willshow a flash message to indicate this decision A flash message is usually a confirmationmessage displayed to end users If the user clicks on the refresh button of his browser themessage will disappear

We also need to modify wwwrootblogprotectedviewspostviewphp furthermore

ltdiv id=commentsgt

lth3gtLeave a Commentlth3gt

ltphp if(Yiiapp()-gtuser-gthasFlash(rsquocommentSubmittedrsquo)) gt

ltdiv class=successgt

ltphp echo Yiiapp()-gtuser-gtgetFlash(rsquocommentSubmittedrsquo) gt

ltdivgt

ltphp else gt

ltphp $this-gtrenderPartial(rsquocomment formrsquoarray(

rsquomodelrsquo=gt$comment

)) gt

ltphp endif gt

ltdivgtlt-- comments --gt

43 Managing Comments 37

In the above code we display the flash message if it is available If not we displaythe comment input form by rendering the partial view wwwrootblogprotectedviews

comment formphp

43 Managing Comments

Comment management includes updating deleting and approving comments These op-erations are implemented as actions in the CommentController class

431 Updating and Deleting Comments

The code generated by yiic for updating and deleting comments remains largely un-changed

432 Approving Comments

When comments are newly created they are in pending approval status and need to beapproved in order to be visible to guest users Approving a comment is mainly aboutchanging the status column of the comment

We create an actionApprove() method in CommentController as follows

public function actionApprove()

if(Yiiapp()-gtrequest-gtisPostRequest)

$comment=$this-gtloadModel()

$comment-gtapprove()

$this-gtredirect(array(rsquoindexrsquo))

else

throw new CHttpException(400rsquoInvalid requestrsquo)

In the above when the approve action is invoked via a POST request we call the approve()

method defined in the Comment model to change the status We then redirect the userbrowser to the page displaying the post that this comment belongs to

We also modify the actionIndex() method of Comment to show all comments We wouldlike to see comments pending approved to show up first

public function actionIndex()

38 4 Comment Management

$dataProvider=new CActiveDataProvider(rsquoCommentrsquo array(

rsquocriteriarsquo=gtarray(

rsquowithrsquo=gtrsquopostrsquo

rsquoorderrsquo=gtrsquotstatus tcreate time DESCrsquo

)

))

$this-gtrender(rsquoindexrsquoarray(

rsquodataProviderrsquo=gt$dataProvider

))

Notice that in the above code because both tbl post and tbl comment have columns statusand create time we need to disambiguate the corresponding column reference by prefixingthem with table alias names As described in the guide the alias for the primary table ina relational query is always t Therefore we are prefixing t to the status and create time

columns in the above code

Like the post index view the index view for CommentController uses [CListView] to displaythe comment list which in turn uses the partial view wwwrootblogprotectedviews

comment viewphp to display the detail of each individual comment We will not go intodetails here Interested readers may refer to the corresponding file in the blog demowwwrootyiidemosblogprotectedviewscomment viewphp

Chapter 5

Portlets

51 Customizing Page Layout

Features like rdquothe most recent commentsrdquo rdquotag cloudrdquo are better to be implemented inportlets A portlet is a pluggable user interface component that renders a fragment ofHTML code In this section we describe how to set up the portlet architecture for ourblog application

Before we go into details about developing portlets we first need to adjust our page layoutso that the portlets can be placed in a side bar

A page layout determines how to share common sections (eg header footer) amongdifferent pages and how to embed contents that are specific for different pages In Yiia page layout is represented as a view under the protectedviewslayouts folder Whenwe call the render() method in a controller the rendering result will be automaticallyembedded into this layout view before it is displayed to end users The default layoutview is named as main which means the wwwrootblogprotectedviewslayoutsmain

php file in our blog application

We could directly modify the main layout to add a side bar However doing so wouldmake some irrelevant pages such as about contact pages to have a side bar which is notalways desirable For this reason we need different layouts for different pages

We thus create two different layouts column1 and column2 The former displays the pagein a single column while the latter displays two columns in the page one of which is theside bar We will use column2 layout for the post and comment pages and column1 forthe rest of the pages Because both column1 and column2 still share the same header andfooter sections we let them to share the main layout

Based on the above description we create the column1 layout as follows

ltphp $this-gtbeginContent(rsquolayoutsmainrsquo) gt

ltdiv class=containergt

40 5 Portlets

ltdiv id=contentgt

ltphp echo $content gt

ltdivgtlt-- content --gt

ltdivgt

ltphp $this-gtendContent() gt

and the column2 layout as follows

ltphp $this-gtbeginContent(rsquolayoutsmainrsquo) gt

ltdiv class=containergt

ltdiv class=span-18gt

ltdiv id=contentgt

ltphp echo $content gt

ltdivgtlt-- content --gt

ltdivgt

ltdiv class=span-6 lastgt

ltdiv id=sidebargt

portlets to be placed here

ltdivgtlt-- sidebar --gt

ltdivgt

ltdivgt

ltphp $this-gtendContent() gt

As we can see both column1 and column2 are enclosed in a pair of beginContent andendContent calls They essentially use the specified view layoutsmain to decorate theenclosed content In all these layout views the variable $content refers to the dynamiccontent to be embedded into the layout

Besides customizing the layout view file we also need to adjust the CSS file wwwroot

blogcssmaincss so that the overall appearance would look like what we see in the blogdemo We will not go into details here

Info The skeleton application generated by the yiic tool uses the BluePrint CSSframework It provides a nice font reset and a robust grid system For more detailson how to use this framework please refer to its documentation

After creating the new layouts we need to set the layout property of controllers to makeuse of them So far we have three controllers SiteController PostController andCommentController We set SiteController to use column1 as the default layout whilethe rest two of the controllers use column2 for example

class PostController extends Controller

52 Creating User Menu Portlet 41

public $layout=rsquocolumn2rsquo

Tip If some view of the controller does not use the same layout as declaredby the layout property it may customize it in the view by a simple assignment$this-gtlayout=rsquonewlayoutrsquo

52 Creating User Menu Portlet

Based on the requirements analysis we need three different portlets the rdquouser menurdquoportlet the rdquotag cloudrdquo portlet and the rdquorecent commentsrdquo portlet We will develop theseportlets by extending the [CPortlet] widget provided by Yii

In this section we will develop our first concrete portlet - the user menu portlet whichdisplays a list of menu items that are only available to authenticated users The menucontains four items

bull Approve Comments a hyperlink that leads to a list of comments pending approval

bull Create New Post a hyperlink that leads to the post creation page

bull Manage Posts a hyperlink that leads to the post management page

bull Logout a link button that would log out the current user

521 Creating UserMenu Class

We create the UserMenu class to represent the logic part of the user menu portlet Theclass is saved in the file wwwrootblogprotectedcomponentsUserMenuphp which has thefollowing content

Yiiimport(rsquoziiwidgetsCPortletrsquo)

class UserMenu extends CPortlet

public function init()

$this-gttitle=CHtmlencode(Yiiapp()-gtuser-gtname)

parentinit()

42 5 Portlets

protected function renderContent()

$this-gtrender(rsquouserMenursquo)

The UserMenu class extends from the CPortlet class that we created previously It overridesboth the init() method and the renderContent() method of CPortlet The former setsthe portlet title to be the name of the current user the latter generates the portlet bodycontent by rendering a view named userMenu

Tip Notice that we have to explicitly include the CPortlet class by calling Yii

import() before we refer to it the first time This is because CPortlet is part of thezii project ndash the official extension library for Yii For performance considerationclasses in this project are not listed as core classes Therefore we have to import itbefore we use it the first time

522 Creating userMenu View

Next we create the userMenu view which is saved in the file wwwrootblogprotected

componentsviewsuserMenuphp

ltulgt

ltligtltphp echo CHtmllink(rsquoCreate New Postrsquoarray(rsquopostcreatersquo)) gtltligt

ltligtltphp echo CHtmllink(rsquoManage Postsrsquoarray(rsquopostadminrsquo)) gtltligt

ltligtltphp echo CHtmllink(rsquoApprove Commentsrsquoarray(rsquocommentindexrsquo))

rsquo (rsquo Commentmodel()-gtpendingCommentCount rsquo)rsquo gtltligt

ltligtltphp echo CHtmllink(rsquoLogoutrsquoarray(rsquositelogoutrsquo)) gtltligt

ltulgt

Info By default view files for a widget should be placed under the views sub-directory of the directory containing the widget class file The file name must bethe same as the view name

523 Using UserMenu Portlet

It is time for us to make use of our newly completed UserMenu portlet We modify thelayout view file wwwrootblogprotectedviewslayoutscolumn2php as follows

53 Creating Tag Cloud Portlet 43

ltdiv id=sidebargt

ltphp if(Yiiapp()-gtuser-gtisGuest) $this-gtwidget(rsquoUserMenursquo) gt

ltdivgt

In the above we call the widget() method to generate and execute an instance of theUserMenu class Because the portlet should only be displayed to authenticated users weonly call widget() when the isGuest property of the current user is false (meaning theuser is authenticated)

524 Testing UserMenu Portlet

Letrsquos test what we have so far

1 Open a browser window and enter the URL httpwwwexamplecomblogindex

php Verify that there is nothing displayed in the side bar section of the page

2 Click on the Login hyperlink and fill out the login form to login If successful verifythat the UserMenu portlet appears in the side bar and the portlet has the usernameas its title

3 Click on the rsquoLogoutrsquo hyperlink in the UserMenu portlet Verify that the logout actionis successful and the UserMenu portlet disappears

525 Summary

What we have created is a portlet that is highly reusable We can easily reuse it in adifferent project with little or no modification Moreover the design of this portlet followsclosely the philosophy that logic and presentation should be separated While we did notpoint this out in the previous sections such practice is used nearly everywhere in a typicalYii application

53 Creating Tag Cloud Portlet

Tag cloud displays a list of post tags with visual decorations hinting the popularity of eachindividual tag

531 Creating TagCloud Class

We create the TagCloud class in the file wwwrootblogprotectedcomponentsTagCloud

php The file has the following content

44 5 Portlets

Yiiimport(rsquoziiwidgetsCPortletrsquo)

class TagCloud extends CPortlet

public $title=rsquoTagsrsquo

public $maxTags=20

protected function renderContent()

$tags=Tagmodel()-gtfindTagWeights($this-gtmaxTags)

foreach($tags as $tag=gt$weight)

$link=CHtmllink(CHtmlencode($tag) array(rsquopostindexrsquorsquotagrsquo=gt$tag))

echo CHtmltag(rsquospanrsquo array(

rsquoclassrsquo=gtrsquotagrsquo

rsquostylersquo=gtfont-size$weightpt) $link)n

Unlike the UserMenu portlet the TagCloud portlet does not use a view Instead its presen-tation is done in the renderContent() method This is because the presentation does notcontain much HTML tags

We display each tag as a hyperlink to the post index page with the corresponding tagparameter The font size of each tag link is adjusted according to their relative weightamong other tags If a tag has higher frequency value than the other it will have a biggerfont size

532 Using TagCloud Portlet

Usage of the TagCloud portlet is very simple We modify the layout file wwwrootblog

protectedviewslayoutsmainphp as follows

ltdiv id=sidebargt

ltphp if(Yiiapp()-gtuser-gtisGuest) $this-gtwidget(rsquoUserMenursquo) gt

ltphp $this-gtwidget(rsquoTagCloudrsquo array(

rsquomaxTagsrsquo=gtYiiapp()-gtparams[rsquotagCloudCountrsquo]

)) gt

ltdivgt

54 Creating Recent Comments Portlet 45

54 Creating Recent Comments Portlet

In this section we create the last portlet that displays a list of comments recently pub-lished

541 Creating RecentComments Class

We create the RecentComments class in the file wwwrootblogprotectedcomponentsRecentCommentsphp The file has the following content

Yiiimport(rsquoziiwidgetsCPortletrsquo)

class RecentComments extends CPortlet

public $title=rsquoRecent Commentsrsquo

public $maxComments=10

public function getRecentComments()

return Commentmodel()-gtfindRecentComments($this-gtmaxComments)

protected function renderContent()

$this-gtrender(rsquorecentCommentsrsquo)

In the above we invoke the findRecentComments method which is defined in the Comment

class as follows

class Comment extends CActiveRecord

public function findRecentComments($limit=10)

return $this-gtwith(rsquopostrsquo)-gtfindAll(array(

rsquoconditionrsquo=gtrsquotstatus=rsquoselfSTATUS APPROVED

rsquoorderrsquo=gtrsquotcreate time DESCrsquo

rsquolimitrsquo=gt$limit

))

46 5 Portlets

542 Creating recentComments View

The recentComments view is saved in the file wwwrootblogprotectedcomponentsviews

recentCommentsphp It simply displays every comment returned by the RecentComments

getRecentComments() method

543 Using RecentComments Portlet

We modify the layout file wwwrootblogprotectedviewslayoutsmainphp to embedthis last portlet

ltdiv id=sidebargt

ltphp if(Yiiapp()-gtuser-gtisGuest) $this-gtwidget(rsquoUserMenursquo) gt

ltphp $this-gtwidget(rsquoTagCloudrsquo array(

rsquomaxTagsrsquo=gtYiiapp()-gtparams[rsquotagCloudCountrsquo]

)) gt

ltphp $this-gtwidget(rsquoRecentCommentsrsquo array(

rsquomaxCommentsrsquo=gtYiiapp()-gtparams[rsquorecentCommentCountrsquo]

)) gt

ltdivgt

Chapter 6

Final Work

61 Beautifying URLs

The URLs linking various pages of our blog application currently look ugly For examplethe URL for the page showing a post looks like the following

indexphpr=postshowampid=1

In this section we describe how to beautify these URLs and make them SEO-friendlyOur goal is to be able to use the following URLs in the application

1 indexphppostsyii leads to the page showing a list of posts with tag yii

2 indexphppost2A+Test+Post leads to the page showing the detail of the postwith ID 2 whose title is A Test Post

3 indexphppostupdate1 leads to the page that allows updating the post with ID1

Note that in the second URL format we include the post title in the URL This is mainlyto make the URL SEO friendly It is said that search engines may also respect the wordsfound in a URL when it is being indexed

To achieve our goal we modify the application configuration as follows

return array(

rsquocomponentsrsquo=gtarray(

rsquourlManagerrsquo=gtarray(

rsquourlFormatrsquo=gtrsquopathrsquo

rsquorulesrsquo=gtarray(

48 6 Final Work

rsquopostltidd+gtlttitlegtrsquo=gtrsquopostviewrsquorsquopostslttaggtrsquo=gtrsquopostindexrsquo

)

)

)

)

In the above we configure the urlManager component by setting its urlFormat propertyto be path and adding a set of rules

The rules are used by urlManager to parse and create the URLs in the desired formatFor example the first rule says that if a URL indexphppostsyii is requested theurlManager component should be responsible to dispatch the request to the route post

index and generate a tag GET parameter with the value yii On the other hand whencreating a URL with the route postindex and parameter tag the urlManager componentwill also use this rule to generate the desired URL indexphppostsyii For this reasonwe say that urlManager is a two-way URL manager

The urlManager component can further beautify our URLs such as hiding indexphp inthe URLs appending suffix like html to the URLs We can obtain these features easilyby configuring various properties of urlManager in the application configuration For moredetails please refer to the Guide

62 Logging Errors

A production Web application often needs sophisticated logging for various events In ourblog application we would like to log the errors occurring when it is being used Sucherrors could be programming mistakes or usersrsquo misuse of the system Logging these errorswill help us to improve the blog application

We enable the error logging by modifying the application configuration as follows

return array(

rsquopreloadrsquo=gtarray(rsquologrsquo)

rsquocomponentsrsquo=gtarray(

rsquologrsquo=gtarray(

rsquoclassrsquo=gtrsquoCLogRouterrsquo

rsquoroutesrsquo=gtarray(

array(

rsquoclassrsquo=gtrsquoCFileLogRoutersquo

rsquolevelsrsquo=gtrsquoerror warningrsquo

63 Final Tune-up and Deployment 49

)

)

)

)

)

With the above configuration if an error or warning occurs detailed information will belogged and saved in a file located under the directory wwwrootblogprotectedruntime

The log component offers more advanced features such as sending log messages to a listof email addresses displaying log messages in JavaScript console window etc For moredetails please refer to the Guide

63 Final Tune-up and Deployment

We are close to finish our blog application Before deployment we would like to do sometune-ups

631 Changing Home Page

We change to use the post list page as the home page We modify the application config-uration as follows

return array(

rsquodefaultControllerrsquo=gtrsquopostrsquo

)

Tip Because PostController already declares list to be its default action whenwe access the home page of the application we will see the result generated by thelist action of the post controller

632 Enabling Schema Caching

Because ActiveRecord relies on the metadata about tables to determine the column infor-mation it takes time to read the metadata and analyze it This may not be a problemduring development stage but for an application running in production mode it is a totalwaste of time if the database schema does not change Therefore we should enable theschema caching by modifying the application configuration as follows

50 6 Final Work

return array(

rsquocomponentsrsquo=gtarray(

rsquocachersquo=gtarray(

rsquoclassrsquo=gtrsquoCDbCachersquo

)

rsquodbrsquo=gtarray(

rsquoclassrsquo=gtrsquosystemdbCDbConnectionrsquo

rsquoconnectionStringrsquo=gtrsquosqlitewwwrootblogprotecteddatablogdbrsquo

rsquoschemaCachingDurationrsquo=gt3600

)

)

)

In the above we first add a cache component which uses a default SQLite database as thecaching storage If our server is equipped with other caching extensions such as APCwe could change to use them as well We also modify the db component by setting itsschemaCachingDuration property to be 3600 which means the parsed database schemadata can remain valid in cache for 3600 seconds

633 Disabling Debugging Mode

We modify the entry script file wwwrootblogindexphp by removing the line definingthe constant YII DEBUG This constant is useful during development stage because it allowsYii to display more debugging information when an error occurs However when theapplication is running in production mode displaying debugging information is not agood idea because it may contain sensitive information such as where the script file islocated and the content in the file etc

634 Deploying the Application

The final deployment process manly involves copying the directory wwwrootblog to thetarget directory The following checklist shows every needed step

1 Install Yii in the target place if it is not available

2 Copy the entire directory wwwrootblog to the target place

3 Edit the entry script file indexphp by pointing the $yii variable to the new Yiibootstrap file

4 Edit the file protectedyiicphp by setting the $yiic variable to be the new Yiiyiicphp file

64 Future Enhancements 51

5 Change the permission of the directories assets and protectedruntime so that theyare writable by the Web server process

64 Future Enhancements

641 Using a Theme

Without writing any code our blog application is already themeable To use a themewe mainly need to develop the theme by writing customized view files in the theme Forexample to use a theme named classic that uses a different page layout we would createa layout view file wwwrootblogthemesclassicviewslayoutsmainphp We also needto change the application configuration to indicate our choice of the classic theme

return array(

rsquothemersquo=gtrsquoclassicrsquo

)

642 Internationalization

We may also internationalize our blog application so that its pages can be displayed indifferent languages This mainly involves efforts in two aspects

First we may create view files in different languages For example for the list pageof PostController we can create a view file wwwrootblogprotectedviewspostzh cn

listphp When the application is configured to use simplified Chinese (the language codeis zh cn) Yii will automatically use this new view file instead of the original one

Second we may create message translations for those messages generated by code Themessage translations should be saved as files under the directory wwwrootblogprotected

messages We also need to modify the code where we use text strings by enclosing themin the method call Yiit()

For more details about internationalization please refer to the Guide

643 Improving Performance with Cache

While the Yii framework itself is very efficient it is not necessarily true that an applicationwritten in Yii efficient There are several places in our blog application that we can improvethe performance For example the tag clould portlet could be one of the performancebottlenecks because it involves complex database query and PHP logic

52 6 Final Work

We can make use of the sophisticated caching feature provided by Yii to improve theperformance One of the most useful components in Yii is COutputCache which caches afragment of page display so that the underlying code generating the fragment does not needto be executed for every request For example in the layout file wwwrootblogprotected

viewslayoutsmainphp we can enclose the tag cloud portlet with COutputCache

ltphp if($this-gtbeginCache(rsquotagCloudrsquo array(rsquodurationrsquo=gt3600))) gt

ltphp $this-gtwidget(rsquoTagCloudrsquo array(

rsquomaxTagsrsquo=gtYiiapp()-gtparams[rsquotagCloudCountrsquo]

)) gt

ltphp $this-gtendCache() gt

With the above code the tag cloud display will be served from cache instead of beinggenerated on-the-fly for every request The cached content will remain valid in cache for3600 seconds

644 Adding New Features

Our blog application only has very basic functionalities To become a complete blogsystem more features are needed for example calendar portlet email notifications postcategorization archived post portlet and so on We will leave the implementation of thesefeatures to interested readers

  • Contents
  • License
  • Getting Started
    • Building a Blog System using Yii
    • Testdriving with Yii
      • Installing Yii
      • Creating Skeleton Application
      • Application Workflow
        • Requirements Analysis
        • Overall Design
          • Initial Prototyping
            • Setting Up Database
              • Creating Database
              • Establishing Database Connection
                • Scaffolding
                • Authenticating User
                • Summary
                  • Post Management
                    • Customizing Post Model
                      • Customizing rules() Method
                      • Customizing relations() Method
                      • Representing Status in Text
                        • Creating and Updating Posts
                          • Customizing Access Control
                          • Customizing create and update Operations
                            • Displaying Posts
                              • Customizing view Operation
                              • Customizing index Operation
                                • Managing Posts
                                  • Listing Posts in Tabular View
                                  • Deleting Posts
                                      • Comment Management
                                        • Customizing Comment Model
                                          • Customizing rules() Method
                                          • Customizing attributeLabels() Method
                                          • Customizing Saving Process
                                            • Creating and Displaying Comments
                                              • Displaying Comments
                                              • Creating Comments
                                                • Managing Comments
                                                  • Updating and Deleting Comments
                                                  • Approving Comments
                                                      • Portlets
                                                        • Customizing Page Layout
                                                        • Creating User Menu Portlet
                                                          • Creating UserMenu Class
                                                          • Creating userMenu View
                                                          • Using UserMenu Portlet
                                                          • Testing UserMenu Portlet
                                                          • Summary
                                                            • Creating Tag Cloud Portlet
                                                              • Creating TagCloud Class
                                                              • Using TagCloud Portlet
                                                                • Creating Recent Comments Portlet
                                                                  • Creating RecentComments Class
                                                                  • Creating recentComments View
                                                                  • Using RecentComments Portlet
                                                                      • Final Work
                                                                        • Beautifying URLs
                                                                        • Logging Errors
                                                                        • Final Tune-up and Deployment
                                                                          • Changing Home Page
                                                                          • Enabling Schema Caching
                                                                          • Disabling Debugging Mode
                                                                          • Deploying the Application
                                                                            • Future Enhancements
                                                                              • Using a Theme
                                                                              • Internationalization
                                                                              • Improving Performance with Cache
                                                                              • Adding New Features
Page 21: Yii Blog 1.1.0 Demo Tutor

14 2 Initial Prototyping

In order to understand better how the above files are used we show in the following theworkflow that occurs in the blog application when displaying a list of posts

1 The user requests the URL httpwwwexamplecomblogindexphpr=posts

2 The entry script is executed by the Web server which creates and initializes anapplication instance to handle the request

3 The application creates an instance of PostController and executes it

4 The PostController instance executes the index action by calling its actionIndex()

method Note that index is the default action if the user does not specify an actionto execute in the URL

5 The actionIndex() method queries database to bring back the list of recent posts

6 The actionIndex() method renders the index view with the post data

23 Authenticating User

Our blog application needs to differentiate between the system owner and guest usersTherefore we need to implement the user authentication feature

As you may have found that the skeleton application already provides user authenticationby checking if the username and password are both demo or admin In this section wewill modify the corresponding code so that the authentication is done against the User

database table

User authentication is performed in a class implementing the IUserIdentity interface Theskeleton application uses the UserIdentity class for this purpose The class is stored inthe file wwwrootblogprotectedcomponentsUserIdentityphp

Tip By convention the name of a class file must be the same as the correspondingclass name suffixed with the extension php Following this convention one canrefer to a class using a path alias For example we can refer to the UserIdentity

class with the alias applicationcomponentsUserIdentity Many APIs in Yiican recognize path aliases (eg YiicreateComponent()) and using path aliasesavoids the necessity of embedding absolute file paths in the code The existence ofthe latter often causes trouble when we deploy an application

We modify the UserIdentity class as follows

23 Authenticating User 15

ltphp

class UserIdentity extends CUserIdentity

private $ id

public function authenticate()

$username=strtolower($this-gtusername)

$user=Usermodel()-gtfind(rsquoLOWER(username)=rsquoarray($username))

if($user===null)

$this-gterrorCode=selfERROR USERNAME INVALID

else if($user-gtvalidatePassword($this-gtpassword))

$this-gterrorCode=selfERROR PASSWORD INVALID

else

$this-gt id=$user-gtid

$this-gtusername=$user-gtusername

$this-gterrorCode=selfERROR NONE

return $this-gterrorCode==selfERROR NONE

public function getId()

return $this-gt id

In the authenticate() method we use the User class to look for a row in the User tablewhose username column is the same as the given username in a case-insensitive mannerRemember that the User class was created using the yiic tool in the prior section Becausethe User class extends from CActiveRecord we can exploit the ActiveRecord feature toaccess the User table in an OOP fashion

In order to check if the user has entered a valid password we invoke the validatePassword

method of the User class We need to modify the file wwwrootblogprotectedmodels

Userphp as follows Note that instead of storing the plain password in the database westore the hash result of the password and a randomly generated salt key When validatingthe user-entered password we should compare the hash results instead

class User extends CActiveRecord

public function validatePassword($password)

return $this-gthashPassword($password$this-gtsalt)===$this-gtpassword

16 2 Initial Prototyping

public function hashPassword($password$salt)

return md5($salt$password)

In the UserIdentity class we also override the getId() method which returns the id valueof the user found in the User table The parent implementation would return the usernameinstead Both the username and id properties will be stored in the user session and maybe accessed via Yiiapp()-gtuser from anywhere in our code

Tip In the UserIdentity class we reference the class CUserIdentity without ex-plicitly including the corresponding class file This is because CUserIdentity is acore class provided by the Yii framework Yii will automatically include the classfile for any core class when it is referenced for the first time

We also do the same with the User class This is because the User class file is placedunder the directory wwwrootblogprotectedmodels which has been added tothe PHP include path according to the following lines found in the applicationconfiguration

return array(

rsquoimportrsquo=gtarray(

rsquoapplicationmodelsrsquo

rsquoapplicationcomponentsrsquo

)

)

The above configuration says that any class whose class file is located under either wwwrootblogprotectedmodels or wwwrootblogprotectedcomponents willbe automatically included when the class is referenced for the first time

The UserIdentity class is mainly used by the LoginForm class to authenticate a user basedon the username and password input collected from the login page The following codefragment shows how UserIdentity is used

$identity=new UserIdentity($username$password)

$identity-gtauthenticate()

switch($identity-gterrorCode)

24 Summary 17

case UserIdentityERROR NONE

Yiiapp()-gtuser-gtlogin($identity)

break

Info People often get confused about identity and the user application componentThe former represents a way of performing authentication while the latter is usedto represent the information related with the current user An application can onlyhave one user component but it can have one or several identity classes dependingon what kind of authentication it supports Once authenticated an identity instancemay pass its state information to the user component so that they are globallyaccessible via user

To test the modified UserIdentity class we can browse the URL httpwwwexample

comblogindexphp and try logging in with the username and password that we store inthe User table If we use the database provided by the blog demo we should be able tologin with username demo and password demo Note that this blog system does not providethe user management feature As a result a user cannot change his account or create anew one through the Web interface The user management feature may be considered asa future enhancement to the blog application

24 Summary

We have completed the milestone 1 Letrsquos summarize what we have done so far

1 We identified the requirements to be fulfilled

2 We installed the Yii framework

3 We created a skeleton application

4 We designed and created the blog database

5 We modified the application configuration by adding the database connection

6 We generated the code that implements the basic CRUD operations for both postsand comments

7 We modified the authentication method to check against the tbl user table

18 2 Initial Prototyping

For a new project most of the time will be spent in step 1 and 4 for this first milestone

Although the code generated by the yiic tool implements fully functional CRUD oper-ations for a database table it often needs to be modified in practical applications Forthis reason in the next two milestone our job is to customize the generated CRUD codeabout posts and comments so that it reaches our initial requirements

In general we first modify the model class file by adding appropriate validation rules anddeclaring relational objects We then modify the controller action and view code for eachindividual CRUD operation

Chapter 3

Post Management

31 Customizing Post Model

The Post model class generated by the yiic tool mainly needs to be modified in two places

bull the rules() method specifies the validation rules for the model attributes

bull the relations() method specifies the related objects

Info A model consists of a list of attributes each associated with a column in thecorresponding database table Attributes can be declared explicitly as class membervariables or implicitly without any declaration

311 Customizing rules() Method

We first specify the validation rules which ensure the attribute values entered by usersare correct before they are saved to the database For example the status attribute ofPost should be an integer 1 2 or 3 The yiic tool also generates validation rules for eachmodel However these rules are based on the table column information and may not beappropriate

Based on the requirement analysis we modify the rules() method as follows

public function rules()

return array(

array(rsquotitle content statusrsquo rsquorequiredrsquo)

array(rsquotitlersquo rsquolengthrsquo rsquomaxrsquo=gt128)

array(rsquostatusrsquo rsquoinrsquo rsquorangersquo=gtarray(123))

array(rsquotagsrsquo rsquomatchrsquo rsquopatternrsquo=gtrsquo^[ws]+$rsquorsquomessagersquo=gtrsquoTags can only contain word charactersrsquo)

20 3 Post Management

array(rsquotagsrsquo rsquonormalizeTagsrsquo)

)

In the above we specify that the title content and status attributes are required thelength of title should not exceed 128 the status attribute value should be 1 (draft) 2(published) or 3 (archived) and the tags attribute should only contain word charactersand commas In addition we use normalizeTags to normalize the user-entered tags so thatthe tags are unique and properly separated with commas

The validators such as required length in and match are all built-in validators providedby Yii The normalizeTags validator is a method-based validator that we need to definein the Post class For more information about how to specify validation rules please referto the Guide

public function normalizeTags($attribute$params)

$this-gttags=Tagarray2string(array unique(Tagstring2array($this-gttags)))

The rules declared in this method are executed one by one when we call the validate() orsave() method of the model instance

Note It is very important to remember that attributes appearing in rules() mustbe those to be entered by end users Other attributes such as id and create time

in the Post model which are set by our code or database should not be in rules()For more details please refer to Securing Attribute Assignments

After making these changes we can visit the post creation page again to verify that thenew validation rules are taking effect

312 Customizing relations() Method

Lastly we customize the relations() method to specify the related objects of a post Bydeclaring these related objects in relations() we can exploit the powerful RelationalActiveRecord (RAR) feature to access the related object information of a post such asits author and comments without the need to write complex SQL JOIN statements

We customize the relations() method as follows

31 Customizing Post Model 21

public function relations()

return array(

rsquoauthorrsquo =gt array(selfBELONGS TO rsquoUserrsquo rsquoauthor idrsquo)

rsquocommentsrsquo =gt array(selfHAS MANY rsquoCommentrsquo rsquopost idrsquo

rsquoconditionrsquo=gtrsquocommentsstatus=rsquoCommentSTATUS APPROVED

rsquoorderrsquo=gtrsquocommentscreate time DESCrsquo)

rsquocommentCountrsquo =gt array(selfSTAT rsquoCommentrsquo rsquopost idrsquo

rsquoconditionrsquo=gtrsquostatus=rsquoCommentSTATUS APPROVED)

)

The above relations state that

bull A post belongs to an author whose class is User and the relationship is establishedbased on the author id attribute value of the post

bull A post has many comments whose class is Comment and the relationship is establishedbased on the post id attribute value of the comments These comments should besorted according to their creation time and the comments must be approved

bull The commentCount relation is a bit special as it returns back an aggregation resultwhich is about how many comments the post has

With the above relation declaration we can easily access the author and comments of apost like the following

$author=$post-gtauthor

echo $author-gtusername

$comments=$post-gtcomments

foreach($comments as $comment)

echo $comment-gtcontent

For more details about how to declare and use relations please refer to the Guide

313 Representing Status in Text

Because the status of a post is stored as an integer in the database we need to provide atextual representation so that it is more intuitive when being displayed to end users In alarge system the similar requirement is very common

22 3 Post Management

As a generic solution we use the tbl lookup table to store the mapping between integervalues and textual representations that are needed by other data objects We modify theLookup model class as follows to more easily access the textual data in the table

class Lookup extends CActiveRecord

private static $ items=array()

public static function items($type)

if(isset(self$ items[$type]))

selfloadItems($type)

return self$ items[$type]

public static function item($type$code)

if(isset(self$ items[$type]))

selfloadItems($type)

return isset(self$ items[$type][$code]) self$ items[$type][$code] false

private static function loadItems($type)

self$ items[$type]=array()

$models=selfmodel()-gtfindAll(array(

rsquoconditionrsquo=gtrsquotype=typersquo

rsquoparamsrsquo=gtarray(rsquotypersquo=gt$type)

rsquoorderrsquo=gtrsquopositionrsquo

))

foreach($models as $model)

self$ items[$type][$model-gtcode]=$model-gtname

Our new code mainly provides two static methods Lookupitems() and Lookupitem()The former returns a list of strings belonging to the specified data type while the latterreturns a particular string for the given data type and data value

Our blog database is pre-populated with two lookup types PostStatus and CommentStatusThe former refers to the possible post statuses while the latter the comment statuses

In order to make our code easier to read we also declare a set of constants to representthe status integer values We should use these constants through our code when referringto the corresponding status values

32 Creating and Updating Posts 23

class Post extends CActiveRecord

const STATUS DRAFT=0

const STATUS PUBLISHED=1

const STATUS ARCHIVED=2

Therefore we can call Lookupitems(rsquoPostStatusrsquo) to get the list of possible post statuses(text strings indexed by the corresponding integer values) and call Lookupitem(rsquoPostStatusrsquoPostSTATUS PUBLISHED) to get the string representation of the published status

32 Creating and Updating Posts

With the Post model ready we need to fine-tune the actions and views for the controllerPostController In this section we first customize the access control of CRUD operationswe then modify the code implementing the create and update operations

321 Customizing Access Control

The first thing we want to do is to customize the access control because the code generatedby yiic does not fit our needs

We modify the accessRules() method in the file wwwrootblogprotectedcontrollers

PostControllerphp as follows

public function accessRules()

return array(

array(rsquoallowrsquo allow all users to perform rsquolistrsquo and rsquoshowrsquo actions

rsquoactionsrsquo=gtarray(rsquoindexrsquo rsquoviewrsquo)

rsquousersrsquo=gtarray(rsquorsquo)

)

array(rsquoallowrsquo allow authenticated users to perform any action

rsquousersrsquo=gtarray(rsquorsquo)

)

array(rsquodenyrsquo deny all users

rsquousersrsquo=gtarray(rsquorsquo)

)

)

The above rules state that all users can access the index and view actions and authenti-cated users can access any actions including the admin action The user should be denied

24 3 Post Management

access in any other scenario Note that these rules are evaluated in the order they arelisted here The first rule matching the current context makes the access decision Forexample if the current user is the system owner who tries to visit the post creation pagethe second rule will match and it will give the access to the user

322 Customizing create and update Operations

The create and update operations are very similar They both need to display an HTMLform to collect user inputs validate them and save them into database The main differ-ence is that the update operation will pre-populate the form with the existing post datafound in the database For this reason the yiic tool generates a partial view wwwroot

blogprotectedviewspost formphp that is embedded in both the create and update

views to render the needed HTML form

We first change the formphp file so that the HTML form only collects the inputs wewant title content tags and status We use plain text fields to collect inputs for thefirst three attributes and a dropdown list to collect input for status The dropdown listoptions are the text displays of the possible post statuses

ltphp echo CHtmlactiveDropDownList($postrsquostatusrsquoLookupitems(rsquoPostStatusrsquo)) gt

In the above we call Lookupitems(rsquoPostStatusrsquo) to bring back the list of post statuses

We then modify the Post class so that it can automatically set some attributes (eg create

time author id) before a post is saved to the database We override the beforeSave()

method as follows

protected function beforeSave()

if(parentbeforeSave())

if($this-gtisNewRecord)

$this-gtcreate time=$this-gtupdate time=time()

$this-gtauthor id=Yiiapp()-gtuser-gtid

else

$this-gtupdate time=time()

return true

else

return false

33 Displaying Posts 25

When we save a post we want to update the tbl tag table to reflect the change of tagfrequencies We can do this work in the afterSave() method which is automaticallyinvoked by Yii after a post is successfully saved into the database

protected function afterSave()

parentafterSave()

Tagmodel()-gtupdateFrequency($this-gt oldTags $this-gttags)

private $ oldTags

protected function afterFind()

parentafterFind()

$this-gt oldTags=$this-gttags

In the implementation because we want to detect if the user changes the tags in case he isupdating an existing post we need to know what the old tags are For this reason we alsowrite the afterFind() method to keep the old tags in the variable oldTags The methodafterFind() is invoked automatically by Yii when an AR record is populated with thedata from database

We are not going to give details of the TagupdateFrequency() method here Interestedreaders may refer to the file wwwrootyiidemosblogprotectedmodelsTagphp

33 Displaying Posts

In our blog application a post may be displayed among a list of posts or by itself Theformer is implemented as the index operation while the latter the view operation In thissection we customize both operations to fulfill our initial requirements

331 Customizing view Operation

The view operation is implemented by the actionView() method in PostController Itsdisplay is generated by the view view with the view file wwwrootblogprotectedviews

postviewphp

Below is the relevant code implementing the view operation in PostController

public function actionView()

26 3 Post Management

$this-gtrender(rsquoviewrsquoarray(

$post=$this-gtloadModel()

$this-gtrender(rsquoviewrsquoarray(

rsquomodelrsquo=gt$post

))

))

private $ model

public function loadModel()

if($this-gt model===null)

if(isset($ GET[rsquoidrsquo]))

if(Yiiapp()-gtuser-gtisGuest)

$condition=rsquostatus=rsquoPostSTATUS PUBLISHED

rsquo OR status=rsquoPOSTSTATUS ARCHIVED

else

$condition=rsquorsquo

$this-gt model=Postmodel()-gtfindbyPk($ GET[rsquoidrsquo] $condition)

if($this-gt model===null)

throw new CHttpException(404rsquoThe requested page does not existrsquo)

return $this-gt model

Our change mainly lies in the loadModel() method In this method we query the Post

table according to the id GET parameter If the post is not found or if it is not publishedor archived (when the user is a guest) we will throw a 404 HTTP error Otherwise thepost object is returned to actionView() which in turn passes the post object to the viewscript for further display

Tip Yii captures HTTP exceptions (instances of CHttpException) and displaysthem in either predefined templates or customized error views The skeleton appli-cation generated by yiic already contains a customized error view in wwwroot

blogprotectedviewssiteerrorphp We can modify this file if we want tofurther customize the error display

The change in the view script is mainly about ajdusting the formatting and styles of thepost display We will not go into details here Interested readers may refer to wwwroot

blogprotectedviewspostviewphp

33 Displaying Posts 27

332 Customizing index Operation

Like the view operation we customize the index operation in two places the actionIndex()

method in PostController and the view file wwwrootblogprotectedviewspostindex

php We mainly need to add the support for displaying a list of posts that are associatedwith a specified tag

Below is the modified actionIndex() method in PostController

public function actionIndex()

$criteria=new CDbCriteria(array(

rsquoconditionrsquo=gtrsquostatus=rsquoPostSTATUS PUBLISHED

rsquoorderrsquo=gtrsquoupdate time DESCrsquo

rsquowithrsquo=gtrsquocommentCountrsquo

))

if(isset($ GET[rsquotagrsquo]))

$criteria-gtaddSearchCondition(rsquotagsrsquo$ GET[rsquotagrsquo])

$dataProvider=new CActiveDataProvider(rsquoPostrsquo array(

rsquopaginationrsquo=gtarray(

rsquopageSizersquo=gtYiiapp()-gtparams[rsquopostsPerPagersquo]

)

rsquocriteriarsquo=gt$criteria

))

$this-gtrender(rsquoindexrsquoarray(

rsquodataProviderrsquo=gt$dataProvider

))

In the above we first create a query criteria for retrieving post list The criteria statesthat only published posts should be returned and they should be sorted according to theirupdate time in descending order Because when displaying a post in the list we want toshow how many comments the post has received in the criteria we also specify to bringback commentCount which if you remember is a relation declared in Postrelations()

In case when a user wants to see posts with a specific tag we would add a search conditionto the criteria to look for the specified tag

Using the query criteria we create a data provider which mainly serves for three purposesFirst it does pagination of the data when too many results may be returned Herewe customize the pagination by setting the page size to be an application parameterpostsPerPage Second it does sorting according to the user request And finally it feedsthe paginated and sorted data to widgets or view code for presentation

28 3 Post Management

After we finish with actionIndex() we modify the index view as follows Our change ismainly about adding the h1 header when the user specifies to display posts with a tag

ltphp if(empty($ GET[rsquotagrsquo])) gt

lth1gtPosts Tagged with ltigtltphp echo CHtmlencode($ GET[rsquotagrsquo]) gtltigtlth1gt

ltphp endif gt

ltphp $this-gtwidget(rsquoziiwidgetsCListViewrsquo array(

rsquodataProviderrsquo=gt$dataProvider

rsquoitemViewrsquo=gtrsquo viewrsquo

rsquotemplatersquo=gtitemsnpager)) gt

Note that in the above we use [CListView] to display the post list This widget requires apartial view to display the detail of each individual post Here we specify the partial viewto be view which means the file wwwrootblogprotectedviewspost viewphp In thisview script we can acccess the post instance being displayed via a local variable named$data

34 Managing Posts

Managing posts mainly refers to listing posts in an administrative view that allows us tosee posts with all statuses updating them and deleting them They are accomplished bythe admin operation and the delete operation respectively The code generated by yiic

does not need much modification Below we mainly explain how these two operations areimplemented

341 Listing Posts in Tabular View

The admin operation shows posts with all statuses in a tabular view The view supportssorting and pagination The following is the actionAdmin() method in PostController

public function actionAdmin()

$dataProvider=new CActiveDataProvider(rsquoPostrsquo array(

rsquosortrsquo=gtarray(

rsquodefaultOrderrsquo=gtrsquostatus update time DESCrsquo

)

))

$this-gtrender(rsquoadminrsquoarray(

rsquodataProviderrsquo=gt$dataProvider

))

34 Managing Posts 29

The above code is very similar to that in actionIndex() The main difference is in thequery criteria Here we do not specify any particular query criteria since we want to seeall posts We do customize the sort property of the data provider so that by default theposts are shown in the order of their statuses and update time

Below is the code for the admin view

lth1gtManage Postslth1gt

ltphp $this-gtwidget(rsquoziiwidgetsgridCGridViewrsquo array(

rsquodataProviderrsquo=gt$dataProvider

rsquocolumnsrsquo=gtarray(

array(

rsquonamersquo=gtrsquotitlersquo

rsquotypersquo=gtrsquorawrsquo

rsquovaluersquo=gtrsquoCHtmllink(CHtmlencode($data-gttitle) $data-gturl)rsquo

)

array(

rsquonamersquo=gtrsquostatusrsquo

rsquovaluersquo=gtrsquoLookupitem(PostStatus$data-gtstatus)rsquo

)

rsquocreate timedatetimersquo

rsquoupdate timedatetimersquo

array(

rsquoclassrsquo=gtrsquoCButtonColumnrsquo

)

)

)) gt

We use [CGridView] to display the posts It allows us to sort by a column and paginatethrough the posts if there are too many to be displayed in a single page Our change ismainly about how to display each column For example for the title column we specifythat it should be displayed as a hyperlink that points to the detailed view of the post

Tip When displaying text we call CHtmlencode() to encode HTML entities init This prevents from cross-site scripting attack

342 Deleting Posts

In the admin data grid there is a delete button in each row Clicking on the button shoulddelete the corresponding post Internally this triggers the delete action implemented asfollows

30 3 Post Management

public function actionDelete()

if(Yiiapp()-gtrequest-gtisPostRequest)

we only allow deletion via POST request

$this-gtloadModel()-gtdelete()

if(isset($ POST[rsquoajaxrsquo]))

$this-gtredirect(array(rsquoindexrsquo))

else

throw new CHttpException(400rsquoInvalid request Please do not repeat this request againrsquo)

The above code is the one generated by the yiic tool without any change We wouldlike to explain a little bit more about the checking on $ POST[rsquoajaxrsquo] The [CGridView]widget has a very nice feature that its sorting pagination and deletion operations are alldone in AJAX mode by default That means the whole page does not get reloaded if anyof the above operations is performed However it is also possible that the widget runs innon-AJAX mode (by setting its ajaxUpdate property to be false or disabling JavaScript onthe client side) It is necessary for the delete action to differentiate these two scenarios ifthe delete request is made via AJAX we should not redirect the user browser otherwisewe should

Deleting a post should also cause the deletion of all comments for that post In additionwe should also update the tbl tag table regarding the tags for the deleted post Both ofthese tasks can be achieved by writing an afterDelete method in the Post model class asfollows

protected function afterDelete()

parentafterDelete()

Commentmodel()-gtdeleteAll(rsquopost id=rsquo$this-gtid)

Tagmodel()-gtupdateFrequency($this-gttags rsquorsquo)

The above code is very straightforward it first deletes all those comments whose post id

is the same as the ID of the deleted post it then updates the tbl tag table for the tags

of the deleted post

34 Managing Posts 31

Tip We have to explicitly delete all comments for the deleted post here becauseSQLite does not really support foreign key constraints In a DBMS that supportsthis constraint (such as MySQL PostgreSQL) the foreign key constraint can be setup such that the DBMS automatically deletes the related comments if the post isdeleted In that case we no longer this explicit deletion call in our code

32 3 Post Management

Chapter 4

Comment Management

41 Customizing Comment Model

For the Comment model we mainly need to customize the rules() and attributeLabels()

methods The attributeLabels() method returns a mapping between attribute namesand attribute labels We do not need to touch relations() since the code generated bythe yiic tool is good enough

411 Customizing rules() Method

We first customize the validation rules generated by the yiic tool The following rules areused for comments

public function rules()

return array(

array(rsquocontent author emailrsquo rsquorequiredrsquo)

array(rsquoauthor email urlrsquo rsquolengthrsquo rsquomaxrsquo=gt128)

array(rsquoemailrsquorsquoemailrsquo)

array(rsquourlrsquorsquourlrsquo)

)

In the above we specify that the author email and content attributes are required thelength of author email and url cannot exceed 128 the email attribute must be a validemail address and the url attribute must be a valid URL

412 Customizing attributeLabels() Method

We then customize the attributeLabels() method to declare the label display for eachmodel attribute This method returns an array consisting of name-label pairs When wecall CHtmlactiveLabel() to display an attribute label

34 4 Comment Management

public function attributeLabels()

return array(

rsquoidrsquo =gt rsquoIdrsquo

rsquocontentrsquo =gt rsquoCommentrsquo

rsquostatusrsquo =gt rsquoStatusrsquo

rsquocreate timersquo =gt rsquoCreate Timersquo

rsquoauthorrsquo =gt rsquoNamersquo

rsquoemailrsquo =gt rsquoEmailrsquo

rsquourlrsquo =gt rsquoWebsitersquo

rsquopost idrsquo =gt rsquoPostrsquo

)

Tip If the label for an attribute is not declared in attributeLabels() an algo-rithm will be used to generate an appropriate label For example a label VerifyCode will be generated for attributes verify code or verifyCode

413 Customizing Saving Process

Because we want to record the creation time of a comment we override the beforeSave()

method of Comment like we do for the Post model

protected function beforeSave()

if(parentbeforeSave())

if($this-gtisNewRecord)

$this-gtcreate time=time()

return true

else

return false

42 Creating and Displaying Comments

In this section we implement the comment display and creation features

421 Displaying Comments

Instead of displaying and creating comments on individual pages we use the post detailpage (generated by the view action of PostController) Below the post content display

42 Creating and Displaying Comments 35

we display first a list of comments belonging to that post and then a comment creationform

In order to display comments on the post detail page we modify the view script wwwrootblogprotectedviewspostviewphp as follows

ltdiv id=commentsgt

ltphp if($model-gtcommentCountgt=1) gt

lth3gt

ltphp echo $model-gtcommentCount rsquocomment(s)rsquo gt

lth3gt

ltphp $this-gtrenderPartial(rsquo commentsrsquoarray(

rsquopostrsquo=gt$model

rsquocommentsrsquo=gt$model-gtcomments

)) gt

ltphp endif gt

ltdivgt

In the above we call renderPartial() to render a partial view named comments to displaythe list of comments belonging to the current post Note that in the view we use theexpression $model-gtcomments to retrieve the comments for the post This is valid becausewe have declared a comments relation in the Post class Evaluating this expression wouldtrigger an implicit JOIN database query to bring back the proper comments This featureis known as lazy relational query

The partial view comments is not very interesting It mainly goes through every commentand displays the detail of it Interested readers may refer to wwwrootyiidemosblog

protectedpost commentsphp

422 Creating Comments

To handle comment creation we first modify the actionView() method of PostController

as follows

public function actionView()

$post=$this-gtloadModel()

$comment=$this-gtnewComment($post)

$this-gtrender(rsquoviewrsquoarray(

rsquomodelrsquo=gt$post

rsquocommentrsquo=gt$comment

))

36 4 Comment Management

protected function newComment($post)

$comment=new Comment

if(isset($ POST[rsquoCommentrsquo]))

$comment-gtattributes=$ POST[rsquoCommentrsquo]

if($post-gtaddComment($comment))

if($comment-gtstatus==CommentSTATUS PENDING)

Yiiapp()-gtuser-gtsetFlash(rsquocommentSubmittedrsquorsquoThank yoursquo)

$this-gtrefresh()

return $comment

In the above we call the newComment() method before we render view In the newComment()

method we generate a Comment instance and check if the comment form is submitted If sowe try to add the comment for the post by calling $post-gtaddComment($comment) If it goesthrough we refresh the post detail page In case the comment needs to be approved we willshow a flash message to indicate this decision A flash message is usually a confirmationmessage displayed to end users If the user clicks on the refresh button of his browser themessage will disappear

We also need to modify wwwrootblogprotectedviewspostviewphp furthermore

ltdiv id=commentsgt

lth3gtLeave a Commentlth3gt

ltphp if(Yiiapp()-gtuser-gthasFlash(rsquocommentSubmittedrsquo)) gt

ltdiv class=successgt

ltphp echo Yiiapp()-gtuser-gtgetFlash(rsquocommentSubmittedrsquo) gt

ltdivgt

ltphp else gt

ltphp $this-gtrenderPartial(rsquocomment formrsquoarray(

rsquomodelrsquo=gt$comment

)) gt

ltphp endif gt

ltdivgtlt-- comments --gt

43 Managing Comments 37

In the above code we display the flash message if it is available If not we displaythe comment input form by rendering the partial view wwwrootblogprotectedviews

comment formphp

43 Managing Comments

Comment management includes updating deleting and approving comments These op-erations are implemented as actions in the CommentController class

431 Updating and Deleting Comments

The code generated by yiic for updating and deleting comments remains largely un-changed

432 Approving Comments

When comments are newly created they are in pending approval status and need to beapproved in order to be visible to guest users Approving a comment is mainly aboutchanging the status column of the comment

We create an actionApprove() method in CommentController as follows

public function actionApprove()

if(Yiiapp()-gtrequest-gtisPostRequest)

$comment=$this-gtloadModel()

$comment-gtapprove()

$this-gtredirect(array(rsquoindexrsquo))

else

throw new CHttpException(400rsquoInvalid requestrsquo)

In the above when the approve action is invoked via a POST request we call the approve()

method defined in the Comment model to change the status We then redirect the userbrowser to the page displaying the post that this comment belongs to

We also modify the actionIndex() method of Comment to show all comments We wouldlike to see comments pending approved to show up first

public function actionIndex()

38 4 Comment Management

$dataProvider=new CActiveDataProvider(rsquoCommentrsquo array(

rsquocriteriarsquo=gtarray(

rsquowithrsquo=gtrsquopostrsquo

rsquoorderrsquo=gtrsquotstatus tcreate time DESCrsquo

)

))

$this-gtrender(rsquoindexrsquoarray(

rsquodataProviderrsquo=gt$dataProvider

))

Notice that in the above code because both tbl post and tbl comment have columns statusand create time we need to disambiguate the corresponding column reference by prefixingthem with table alias names As described in the guide the alias for the primary table ina relational query is always t Therefore we are prefixing t to the status and create time

columns in the above code

Like the post index view the index view for CommentController uses [CListView] to displaythe comment list which in turn uses the partial view wwwrootblogprotectedviews

comment viewphp to display the detail of each individual comment We will not go intodetails here Interested readers may refer to the corresponding file in the blog demowwwrootyiidemosblogprotectedviewscomment viewphp

Chapter 5

Portlets

51 Customizing Page Layout

Features like rdquothe most recent commentsrdquo rdquotag cloudrdquo are better to be implemented inportlets A portlet is a pluggable user interface component that renders a fragment ofHTML code In this section we describe how to set up the portlet architecture for ourblog application

Before we go into details about developing portlets we first need to adjust our page layoutso that the portlets can be placed in a side bar

A page layout determines how to share common sections (eg header footer) amongdifferent pages and how to embed contents that are specific for different pages In Yiia page layout is represented as a view under the protectedviewslayouts folder Whenwe call the render() method in a controller the rendering result will be automaticallyembedded into this layout view before it is displayed to end users The default layoutview is named as main which means the wwwrootblogprotectedviewslayoutsmain

php file in our blog application

We could directly modify the main layout to add a side bar However doing so wouldmake some irrelevant pages such as about contact pages to have a side bar which is notalways desirable For this reason we need different layouts for different pages

We thus create two different layouts column1 and column2 The former displays the pagein a single column while the latter displays two columns in the page one of which is theside bar We will use column2 layout for the post and comment pages and column1 forthe rest of the pages Because both column1 and column2 still share the same header andfooter sections we let them to share the main layout

Based on the above description we create the column1 layout as follows

ltphp $this-gtbeginContent(rsquolayoutsmainrsquo) gt

ltdiv class=containergt

40 5 Portlets

ltdiv id=contentgt

ltphp echo $content gt

ltdivgtlt-- content --gt

ltdivgt

ltphp $this-gtendContent() gt

and the column2 layout as follows

ltphp $this-gtbeginContent(rsquolayoutsmainrsquo) gt

ltdiv class=containergt

ltdiv class=span-18gt

ltdiv id=contentgt

ltphp echo $content gt

ltdivgtlt-- content --gt

ltdivgt

ltdiv class=span-6 lastgt

ltdiv id=sidebargt

portlets to be placed here

ltdivgtlt-- sidebar --gt

ltdivgt

ltdivgt

ltphp $this-gtendContent() gt

As we can see both column1 and column2 are enclosed in a pair of beginContent andendContent calls They essentially use the specified view layoutsmain to decorate theenclosed content In all these layout views the variable $content refers to the dynamiccontent to be embedded into the layout

Besides customizing the layout view file we also need to adjust the CSS file wwwroot

blogcssmaincss so that the overall appearance would look like what we see in the blogdemo We will not go into details here

Info The skeleton application generated by the yiic tool uses the BluePrint CSSframework It provides a nice font reset and a robust grid system For more detailson how to use this framework please refer to its documentation

After creating the new layouts we need to set the layout property of controllers to makeuse of them So far we have three controllers SiteController PostController andCommentController We set SiteController to use column1 as the default layout whilethe rest two of the controllers use column2 for example

class PostController extends Controller

52 Creating User Menu Portlet 41

public $layout=rsquocolumn2rsquo

Tip If some view of the controller does not use the same layout as declaredby the layout property it may customize it in the view by a simple assignment$this-gtlayout=rsquonewlayoutrsquo

52 Creating User Menu Portlet

Based on the requirements analysis we need three different portlets the rdquouser menurdquoportlet the rdquotag cloudrdquo portlet and the rdquorecent commentsrdquo portlet We will develop theseportlets by extending the [CPortlet] widget provided by Yii

In this section we will develop our first concrete portlet - the user menu portlet whichdisplays a list of menu items that are only available to authenticated users The menucontains four items

bull Approve Comments a hyperlink that leads to a list of comments pending approval

bull Create New Post a hyperlink that leads to the post creation page

bull Manage Posts a hyperlink that leads to the post management page

bull Logout a link button that would log out the current user

521 Creating UserMenu Class

We create the UserMenu class to represent the logic part of the user menu portlet Theclass is saved in the file wwwrootblogprotectedcomponentsUserMenuphp which has thefollowing content

Yiiimport(rsquoziiwidgetsCPortletrsquo)

class UserMenu extends CPortlet

public function init()

$this-gttitle=CHtmlencode(Yiiapp()-gtuser-gtname)

parentinit()

42 5 Portlets

protected function renderContent()

$this-gtrender(rsquouserMenursquo)

The UserMenu class extends from the CPortlet class that we created previously It overridesboth the init() method and the renderContent() method of CPortlet The former setsthe portlet title to be the name of the current user the latter generates the portlet bodycontent by rendering a view named userMenu

Tip Notice that we have to explicitly include the CPortlet class by calling Yii

import() before we refer to it the first time This is because CPortlet is part of thezii project ndash the official extension library for Yii For performance considerationclasses in this project are not listed as core classes Therefore we have to import itbefore we use it the first time

522 Creating userMenu View

Next we create the userMenu view which is saved in the file wwwrootblogprotected

componentsviewsuserMenuphp

ltulgt

ltligtltphp echo CHtmllink(rsquoCreate New Postrsquoarray(rsquopostcreatersquo)) gtltligt

ltligtltphp echo CHtmllink(rsquoManage Postsrsquoarray(rsquopostadminrsquo)) gtltligt

ltligtltphp echo CHtmllink(rsquoApprove Commentsrsquoarray(rsquocommentindexrsquo))

rsquo (rsquo Commentmodel()-gtpendingCommentCount rsquo)rsquo gtltligt

ltligtltphp echo CHtmllink(rsquoLogoutrsquoarray(rsquositelogoutrsquo)) gtltligt

ltulgt

Info By default view files for a widget should be placed under the views sub-directory of the directory containing the widget class file The file name must bethe same as the view name

523 Using UserMenu Portlet

It is time for us to make use of our newly completed UserMenu portlet We modify thelayout view file wwwrootblogprotectedviewslayoutscolumn2php as follows

53 Creating Tag Cloud Portlet 43

ltdiv id=sidebargt

ltphp if(Yiiapp()-gtuser-gtisGuest) $this-gtwidget(rsquoUserMenursquo) gt

ltdivgt

In the above we call the widget() method to generate and execute an instance of theUserMenu class Because the portlet should only be displayed to authenticated users weonly call widget() when the isGuest property of the current user is false (meaning theuser is authenticated)

524 Testing UserMenu Portlet

Letrsquos test what we have so far

1 Open a browser window and enter the URL httpwwwexamplecomblogindex

php Verify that there is nothing displayed in the side bar section of the page

2 Click on the Login hyperlink and fill out the login form to login If successful verifythat the UserMenu portlet appears in the side bar and the portlet has the usernameas its title

3 Click on the rsquoLogoutrsquo hyperlink in the UserMenu portlet Verify that the logout actionis successful and the UserMenu portlet disappears

525 Summary

What we have created is a portlet that is highly reusable We can easily reuse it in adifferent project with little or no modification Moreover the design of this portlet followsclosely the philosophy that logic and presentation should be separated While we did notpoint this out in the previous sections such practice is used nearly everywhere in a typicalYii application

53 Creating Tag Cloud Portlet

Tag cloud displays a list of post tags with visual decorations hinting the popularity of eachindividual tag

531 Creating TagCloud Class

We create the TagCloud class in the file wwwrootblogprotectedcomponentsTagCloud

php The file has the following content

44 5 Portlets

Yiiimport(rsquoziiwidgetsCPortletrsquo)

class TagCloud extends CPortlet

public $title=rsquoTagsrsquo

public $maxTags=20

protected function renderContent()

$tags=Tagmodel()-gtfindTagWeights($this-gtmaxTags)

foreach($tags as $tag=gt$weight)

$link=CHtmllink(CHtmlencode($tag) array(rsquopostindexrsquorsquotagrsquo=gt$tag))

echo CHtmltag(rsquospanrsquo array(

rsquoclassrsquo=gtrsquotagrsquo

rsquostylersquo=gtfont-size$weightpt) $link)n

Unlike the UserMenu portlet the TagCloud portlet does not use a view Instead its presen-tation is done in the renderContent() method This is because the presentation does notcontain much HTML tags

We display each tag as a hyperlink to the post index page with the corresponding tagparameter The font size of each tag link is adjusted according to their relative weightamong other tags If a tag has higher frequency value than the other it will have a biggerfont size

532 Using TagCloud Portlet

Usage of the TagCloud portlet is very simple We modify the layout file wwwrootblog

protectedviewslayoutsmainphp as follows

ltdiv id=sidebargt

ltphp if(Yiiapp()-gtuser-gtisGuest) $this-gtwidget(rsquoUserMenursquo) gt

ltphp $this-gtwidget(rsquoTagCloudrsquo array(

rsquomaxTagsrsquo=gtYiiapp()-gtparams[rsquotagCloudCountrsquo]

)) gt

ltdivgt

54 Creating Recent Comments Portlet 45

54 Creating Recent Comments Portlet

In this section we create the last portlet that displays a list of comments recently pub-lished

541 Creating RecentComments Class

We create the RecentComments class in the file wwwrootblogprotectedcomponentsRecentCommentsphp The file has the following content

Yiiimport(rsquoziiwidgetsCPortletrsquo)

class RecentComments extends CPortlet

public $title=rsquoRecent Commentsrsquo

public $maxComments=10

public function getRecentComments()

return Commentmodel()-gtfindRecentComments($this-gtmaxComments)

protected function renderContent()

$this-gtrender(rsquorecentCommentsrsquo)

In the above we invoke the findRecentComments method which is defined in the Comment

class as follows

class Comment extends CActiveRecord

public function findRecentComments($limit=10)

return $this-gtwith(rsquopostrsquo)-gtfindAll(array(

rsquoconditionrsquo=gtrsquotstatus=rsquoselfSTATUS APPROVED

rsquoorderrsquo=gtrsquotcreate time DESCrsquo

rsquolimitrsquo=gt$limit

))

46 5 Portlets

542 Creating recentComments View

The recentComments view is saved in the file wwwrootblogprotectedcomponentsviews

recentCommentsphp It simply displays every comment returned by the RecentComments

getRecentComments() method

543 Using RecentComments Portlet

We modify the layout file wwwrootblogprotectedviewslayoutsmainphp to embedthis last portlet

ltdiv id=sidebargt

ltphp if(Yiiapp()-gtuser-gtisGuest) $this-gtwidget(rsquoUserMenursquo) gt

ltphp $this-gtwidget(rsquoTagCloudrsquo array(

rsquomaxTagsrsquo=gtYiiapp()-gtparams[rsquotagCloudCountrsquo]

)) gt

ltphp $this-gtwidget(rsquoRecentCommentsrsquo array(

rsquomaxCommentsrsquo=gtYiiapp()-gtparams[rsquorecentCommentCountrsquo]

)) gt

ltdivgt

Chapter 6

Final Work

61 Beautifying URLs

The URLs linking various pages of our blog application currently look ugly For examplethe URL for the page showing a post looks like the following

indexphpr=postshowampid=1

In this section we describe how to beautify these URLs and make them SEO-friendlyOur goal is to be able to use the following URLs in the application

1 indexphppostsyii leads to the page showing a list of posts with tag yii

2 indexphppost2A+Test+Post leads to the page showing the detail of the postwith ID 2 whose title is A Test Post

3 indexphppostupdate1 leads to the page that allows updating the post with ID1

Note that in the second URL format we include the post title in the URL This is mainlyto make the URL SEO friendly It is said that search engines may also respect the wordsfound in a URL when it is being indexed

To achieve our goal we modify the application configuration as follows

return array(

rsquocomponentsrsquo=gtarray(

rsquourlManagerrsquo=gtarray(

rsquourlFormatrsquo=gtrsquopathrsquo

rsquorulesrsquo=gtarray(

48 6 Final Work

rsquopostltidd+gtlttitlegtrsquo=gtrsquopostviewrsquorsquopostslttaggtrsquo=gtrsquopostindexrsquo

)

)

)

)

In the above we configure the urlManager component by setting its urlFormat propertyto be path and adding a set of rules

The rules are used by urlManager to parse and create the URLs in the desired formatFor example the first rule says that if a URL indexphppostsyii is requested theurlManager component should be responsible to dispatch the request to the route post

index and generate a tag GET parameter with the value yii On the other hand whencreating a URL with the route postindex and parameter tag the urlManager componentwill also use this rule to generate the desired URL indexphppostsyii For this reasonwe say that urlManager is a two-way URL manager

The urlManager component can further beautify our URLs such as hiding indexphp inthe URLs appending suffix like html to the URLs We can obtain these features easilyby configuring various properties of urlManager in the application configuration For moredetails please refer to the Guide

62 Logging Errors

A production Web application often needs sophisticated logging for various events In ourblog application we would like to log the errors occurring when it is being used Sucherrors could be programming mistakes or usersrsquo misuse of the system Logging these errorswill help us to improve the blog application

We enable the error logging by modifying the application configuration as follows

return array(

rsquopreloadrsquo=gtarray(rsquologrsquo)

rsquocomponentsrsquo=gtarray(

rsquologrsquo=gtarray(

rsquoclassrsquo=gtrsquoCLogRouterrsquo

rsquoroutesrsquo=gtarray(

array(

rsquoclassrsquo=gtrsquoCFileLogRoutersquo

rsquolevelsrsquo=gtrsquoerror warningrsquo

63 Final Tune-up and Deployment 49

)

)

)

)

)

With the above configuration if an error or warning occurs detailed information will belogged and saved in a file located under the directory wwwrootblogprotectedruntime

The log component offers more advanced features such as sending log messages to a listof email addresses displaying log messages in JavaScript console window etc For moredetails please refer to the Guide

63 Final Tune-up and Deployment

We are close to finish our blog application Before deployment we would like to do sometune-ups

631 Changing Home Page

We change to use the post list page as the home page We modify the application config-uration as follows

return array(

rsquodefaultControllerrsquo=gtrsquopostrsquo

)

Tip Because PostController already declares list to be its default action whenwe access the home page of the application we will see the result generated by thelist action of the post controller

632 Enabling Schema Caching

Because ActiveRecord relies on the metadata about tables to determine the column infor-mation it takes time to read the metadata and analyze it This may not be a problemduring development stage but for an application running in production mode it is a totalwaste of time if the database schema does not change Therefore we should enable theschema caching by modifying the application configuration as follows

50 6 Final Work

return array(

rsquocomponentsrsquo=gtarray(

rsquocachersquo=gtarray(

rsquoclassrsquo=gtrsquoCDbCachersquo

)

rsquodbrsquo=gtarray(

rsquoclassrsquo=gtrsquosystemdbCDbConnectionrsquo

rsquoconnectionStringrsquo=gtrsquosqlitewwwrootblogprotecteddatablogdbrsquo

rsquoschemaCachingDurationrsquo=gt3600

)

)

)

In the above we first add a cache component which uses a default SQLite database as thecaching storage If our server is equipped with other caching extensions such as APCwe could change to use them as well We also modify the db component by setting itsschemaCachingDuration property to be 3600 which means the parsed database schemadata can remain valid in cache for 3600 seconds

633 Disabling Debugging Mode

We modify the entry script file wwwrootblogindexphp by removing the line definingthe constant YII DEBUG This constant is useful during development stage because it allowsYii to display more debugging information when an error occurs However when theapplication is running in production mode displaying debugging information is not agood idea because it may contain sensitive information such as where the script file islocated and the content in the file etc

634 Deploying the Application

The final deployment process manly involves copying the directory wwwrootblog to thetarget directory The following checklist shows every needed step

1 Install Yii in the target place if it is not available

2 Copy the entire directory wwwrootblog to the target place

3 Edit the entry script file indexphp by pointing the $yii variable to the new Yiibootstrap file

4 Edit the file protectedyiicphp by setting the $yiic variable to be the new Yiiyiicphp file

64 Future Enhancements 51

5 Change the permission of the directories assets and protectedruntime so that theyare writable by the Web server process

64 Future Enhancements

641 Using a Theme

Without writing any code our blog application is already themeable To use a themewe mainly need to develop the theme by writing customized view files in the theme Forexample to use a theme named classic that uses a different page layout we would createa layout view file wwwrootblogthemesclassicviewslayoutsmainphp We also needto change the application configuration to indicate our choice of the classic theme

return array(

rsquothemersquo=gtrsquoclassicrsquo

)

642 Internationalization

We may also internationalize our blog application so that its pages can be displayed indifferent languages This mainly involves efforts in two aspects

First we may create view files in different languages For example for the list pageof PostController we can create a view file wwwrootblogprotectedviewspostzh cn

listphp When the application is configured to use simplified Chinese (the language codeis zh cn) Yii will automatically use this new view file instead of the original one

Second we may create message translations for those messages generated by code Themessage translations should be saved as files under the directory wwwrootblogprotected

messages We also need to modify the code where we use text strings by enclosing themin the method call Yiit()

For more details about internationalization please refer to the Guide

643 Improving Performance with Cache

While the Yii framework itself is very efficient it is not necessarily true that an applicationwritten in Yii efficient There are several places in our blog application that we can improvethe performance For example the tag clould portlet could be one of the performancebottlenecks because it involves complex database query and PHP logic

52 6 Final Work

We can make use of the sophisticated caching feature provided by Yii to improve theperformance One of the most useful components in Yii is COutputCache which caches afragment of page display so that the underlying code generating the fragment does not needto be executed for every request For example in the layout file wwwrootblogprotected

viewslayoutsmainphp we can enclose the tag cloud portlet with COutputCache

ltphp if($this-gtbeginCache(rsquotagCloudrsquo array(rsquodurationrsquo=gt3600))) gt

ltphp $this-gtwidget(rsquoTagCloudrsquo array(

rsquomaxTagsrsquo=gtYiiapp()-gtparams[rsquotagCloudCountrsquo]

)) gt

ltphp $this-gtendCache() gt

With the above code the tag cloud display will be served from cache instead of beinggenerated on-the-fly for every request The cached content will remain valid in cache for3600 seconds

644 Adding New Features

Our blog application only has very basic functionalities To become a complete blogsystem more features are needed for example calendar portlet email notifications postcategorization archived post portlet and so on We will leave the implementation of thesefeatures to interested readers

  • Contents
  • License
  • Getting Started
    • Building a Blog System using Yii
    • Testdriving with Yii
      • Installing Yii
      • Creating Skeleton Application
      • Application Workflow
        • Requirements Analysis
        • Overall Design
          • Initial Prototyping
            • Setting Up Database
              • Creating Database
              • Establishing Database Connection
                • Scaffolding
                • Authenticating User
                • Summary
                  • Post Management
                    • Customizing Post Model
                      • Customizing rules() Method
                      • Customizing relations() Method
                      • Representing Status in Text
                        • Creating and Updating Posts
                          • Customizing Access Control
                          • Customizing create and update Operations
                            • Displaying Posts
                              • Customizing view Operation
                              • Customizing index Operation
                                • Managing Posts
                                  • Listing Posts in Tabular View
                                  • Deleting Posts
                                      • Comment Management
                                        • Customizing Comment Model
                                          • Customizing rules() Method
                                          • Customizing attributeLabels() Method
                                          • Customizing Saving Process
                                            • Creating and Displaying Comments
                                              • Displaying Comments
                                              • Creating Comments
                                                • Managing Comments
                                                  • Updating and Deleting Comments
                                                  • Approving Comments
                                                      • Portlets
                                                        • Customizing Page Layout
                                                        • Creating User Menu Portlet
                                                          • Creating UserMenu Class
                                                          • Creating userMenu View
                                                          • Using UserMenu Portlet
                                                          • Testing UserMenu Portlet
                                                          • Summary
                                                            • Creating Tag Cloud Portlet
                                                              • Creating TagCloud Class
                                                              • Using TagCloud Portlet
                                                                • Creating Recent Comments Portlet
                                                                  • Creating RecentComments Class
                                                                  • Creating recentComments View
                                                                  • Using RecentComments Portlet
                                                                      • Final Work
                                                                        • Beautifying URLs
                                                                        • Logging Errors
                                                                        • Final Tune-up and Deployment
                                                                          • Changing Home Page
                                                                          • Enabling Schema Caching
                                                                          • Disabling Debugging Mode
                                                                          • Deploying the Application
                                                                            • Future Enhancements
                                                                              • Using a Theme
                                                                              • Internationalization
                                                                              • Improving Performance with Cache
                                                                              • Adding New Features
Page 22: Yii Blog 1.1.0 Demo Tutor

23 Authenticating User 15

ltphp

class UserIdentity extends CUserIdentity

private $ id

public function authenticate()

$username=strtolower($this-gtusername)

$user=Usermodel()-gtfind(rsquoLOWER(username)=rsquoarray($username))

if($user===null)

$this-gterrorCode=selfERROR USERNAME INVALID

else if($user-gtvalidatePassword($this-gtpassword))

$this-gterrorCode=selfERROR PASSWORD INVALID

else

$this-gt id=$user-gtid

$this-gtusername=$user-gtusername

$this-gterrorCode=selfERROR NONE

return $this-gterrorCode==selfERROR NONE

public function getId()

return $this-gt id

In the authenticate() method we use the User class to look for a row in the User tablewhose username column is the same as the given username in a case-insensitive mannerRemember that the User class was created using the yiic tool in the prior section Becausethe User class extends from CActiveRecord we can exploit the ActiveRecord feature toaccess the User table in an OOP fashion

In order to check if the user has entered a valid password we invoke the validatePassword

method of the User class We need to modify the file wwwrootblogprotectedmodels

Userphp as follows Note that instead of storing the plain password in the database westore the hash result of the password and a randomly generated salt key When validatingthe user-entered password we should compare the hash results instead

class User extends CActiveRecord

public function validatePassword($password)

return $this-gthashPassword($password$this-gtsalt)===$this-gtpassword

16 2 Initial Prototyping

public function hashPassword($password$salt)

return md5($salt$password)

In the UserIdentity class we also override the getId() method which returns the id valueof the user found in the User table The parent implementation would return the usernameinstead Both the username and id properties will be stored in the user session and maybe accessed via Yiiapp()-gtuser from anywhere in our code

Tip In the UserIdentity class we reference the class CUserIdentity without ex-plicitly including the corresponding class file This is because CUserIdentity is acore class provided by the Yii framework Yii will automatically include the classfile for any core class when it is referenced for the first time

We also do the same with the User class This is because the User class file is placedunder the directory wwwrootblogprotectedmodels which has been added tothe PHP include path according to the following lines found in the applicationconfiguration

return array(

rsquoimportrsquo=gtarray(

rsquoapplicationmodelsrsquo

rsquoapplicationcomponentsrsquo

)

)

The above configuration says that any class whose class file is located under either wwwrootblogprotectedmodels or wwwrootblogprotectedcomponents willbe automatically included when the class is referenced for the first time

The UserIdentity class is mainly used by the LoginForm class to authenticate a user basedon the username and password input collected from the login page The following codefragment shows how UserIdentity is used

$identity=new UserIdentity($username$password)

$identity-gtauthenticate()

switch($identity-gterrorCode)

24 Summary 17

case UserIdentityERROR NONE

Yiiapp()-gtuser-gtlogin($identity)

break

Info People often get confused about identity and the user application componentThe former represents a way of performing authentication while the latter is usedto represent the information related with the current user An application can onlyhave one user component but it can have one or several identity classes dependingon what kind of authentication it supports Once authenticated an identity instancemay pass its state information to the user component so that they are globallyaccessible via user

To test the modified UserIdentity class we can browse the URL httpwwwexample

comblogindexphp and try logging in with the username and password that we store inthe User table If we use the database provided by the blog demo we should be able tologin with username demo and password demo Note that this blog system does not providethe user management feature As a result a user cannot change his account or create anew one through the Web interface The user management feature may be considered asa future enhancement to the blog application

24 Summary

We have completed the milestone 1 Letrsquos summarize what we have done so far

1 We identified the requirements to be fulfilled

2 We installed the Yii framework

3 We created a skeleton application

4 We designed and created the blog database

5 We modified the application configuration by adding the database connection

6 We generated the code that implements the basic CRUD operations for both postsand comments

7 We modified the authentication method to check against the tbl user table

18 2 Initial Prototyping

For a new project most of the time will be spent in step 1 and 4 for this first milestone

Although the code generated by the yiic tool implements fully functional CRUD oper-ations for a database table it often needs to be modified in practical applications Forthis reason in the next two milestone our job is to customize the generated CRUD codeabout posts and comments so that it reaches our initial requirements

In general we first modify the model class file by adding appropriate validation rules anddeclaring relational objects We then modify the controller action and view code for eachindividual CRUD operation

Chapter 3

Post Management

31 Customizing Post Model

The Post model class generated by the yiic tool mainly needs to be modified in two places

bull the rules() method specifies the validation rules for the model attributes

bull the relations() method specifies the related objects

Info A model consists of a list of attributes each associated with a column in thecorresponding database table Attributes can be declared explicitly as class membervariables or implicitly without any declaration

311 Customizing rules() Method

We first specify the validation rules which ensure the attribute values entered by usersare correct before they are saved to the database For example the status attribute ofPost should be an integer 1 2 or 3 The yiic tool also generates validation rules for eachmodel However these rules are based on the table column information and may not beappropriate

Based on the requirement analysis we modify the rules() method as follows

public function rules()

return array(

array(rsquotitle content statusrsquo rsquorequiredrsquo)

array(rsquotitlersquo rsquolengthrsquo rsquomaxrsquo=gt128)

array(rsquostatusrsquo rsquoinrsquo rsquorangersquo=gtarray(123))

array(rsquotagsrsquo rsquomatchrsquo rsquopatternrsquo=gtrsquo^[ws]+$rsquorsquomessagersquo=gtrsquoTags can only contain word charactersrsquo)

20 3 Post Management

array(rsquotagsrsquo rsquonormalizeTagsrsquo)

)

In the above we specify that the title content and status attributes are required thelength of title should not exceed 128 the status attribute value should be 1 (draft) 2(published) or 3 (archived) and the tags attribute should only contain word charactersand commas In addition we use normalizeTags to normalize the user-entered tags so thatthe tags are unique and properly separated with commas

The validators such as required length in and match are all built-in validators providedby Yii The normalizeTags validator is a method-based validator that we need to definein the Post class For more information about how to specify validation rules please referto the Guide

public function normalizeTags($attribute$params)

$this-gttags=Tagarray2string(array unique(Tagstring2array($this-gttags)))

The rules declared in this method are executed one by one when we call the validate() orsave() method of the model instance

Note It is very important to remember that attributes appearing in rules() mustbe those to be entered by end users Other attributes such as id and create time

in the Post model which are set by our code or database should not be in rules()For more details please refer to Securing Attribute Assignments

After making these changes we can visit the post creation page again to verify that thenew validation rules are taking effect

312 Customizing relations() Method

Lastly we customize the relations() method to specify the related objects of a post Bydeclaring these related objects in relations() we can exploit the powerful RelationalActiveRecord (RAR) feature to access the related object information of a post such asits author and comments without the need to write complex SQL JOIN statements

We customize the relations() method as follows

31 Customizing Post Model 21

public function relations()

return array(

rsquoauthorrsquo =gt array(selfBELONGS TO rsquoUserrsquo rsquoauthor idrsquo)

rsquocommentsrsquo =gt array(selfHAS MANY rsquoCommentrsquo rsquopost idrsquo

rsquoconditionrsquo=gtrsquocommentsstatus=rsquoCommentSTATUS APPROVED

rsquoorderrsquo=gtrsquocommentscreate time DESCrsquo)

rsquocommentCountrsquo =gt array(selfSTAT rsquoCommentrsquo rsquopost idrsquo

rsquoconditionrsquo=gtrsquostatus=rsquoCommentSTATUS APPROVED)

)

The above relations state that

bull A post belongs to an author whose class is User and the relationship is establishedbased on the author id attribute value of the post

bull A post has many comments whose class is Comment and the relationship is establishedbased on the post id attribute value of the comments These comments should besorted according to their creation time and the comments must be approved

bull The commentCount relation is a bit special as it returns back an aggregation resultwhich is about how many comments the post has

With the above relation declaration we can easily access the author and comments of apost like the following

$author=$post-gtauthor

echo $author-gtusername

$comments=$post-gtcomments

foreach($comments as $comment)

echo $comment-gtcontent

For more details about how to declare and use relations please refer to the Guide

313 Representing Status in Text

Because the status of a post is stored as an integer in the database we need to provide atextual representation so that it is more intuitive when being displayed to end users In alarge system the similar requirement is very common

22 3 Post Management

As a generic solution we use the tbl lookup table to store the mapping between integervalues and textual representations that are needed by other data objects We modify theLookup model class as follows to more easily access the textual data in the table

class Lookup extends CActiveRecord

private static $ items=array()

public static function items($type)

if(isset(self$ items[$type]))

selfloadItems($type)

return self$ items[$type]

public static function item($type$code)

if(isset(self$ items[$type]))

selfloadItems($type)

return isset(self$ items[$type][$code]) self$ items[$type][$code] false

private static function loadItems($type)

self$ items[$type]=array()

$models=selfmodel()-gtfindAll(array(

rsquoconditionrsquo=gtrsquotype=typersquo

rsquoparamsrsquo=gtarray(rsquotypersquo=gt$type)

rsquoorderrsquo=gtrsquopositionrsquo

))

foreach($models as $model)

self$ items[$type][$model-gtcode]=$model-gtname

Our new code mainly provides two static methods Lookupitems() and Lookupitem()The former returns a list of strings belonging to the specified data type while the latterreturns a particular string for the given data type and data value

Our blog database is pre-populated with two lookup types PostStatus and CommentStatusThe former refers to the possible post statuses while the latter the comment statuses

In order to make our code easier to read we also declare a set of constants to representthe status integer values We should use these constants through our code when referringto the corresponding status values

32 Creating and Updating Posts 23

class Post extends CActiveRecord

const STATUS DRAFT=0

const STATUS PUBLISHED=1

const STATUS ARCHIVED=2

Therefore we can call Lookupitems(rsquoPostStatusrsquo) to get the list of possible post statuses(text strings indexed by the corresponding integer values) and call Lookupitem(rsquoPostStatusrsquoPostSTATUS PUBLISHED) to get the string representation of the published status

32 Creating and Updating Posts

With the Post model ready we need to fine-tune the actions and views for the controllerPostController In this section we first customize the access control of CRUD operationswe then modify the code implementing the create and update operations

321 Customizing Access Control

The first thing we want to do is to customize the access control because the code generatedby yiic does not fit our needs

We modify the accessRules() method in the file wwwrootblogprotectedcontrollers

PostControllerphp as follows

public function accessRules()

return array(

array(rsquoallowrsquo allow all users to perform rsquolistrsquo and rsquoshowrsquo actions

rsquoactionsrsquo=gtarray(rsquoindexrsquo rsquoviewrsquo)

rsquousersrsquo=gtarray(rsquorsquo)

)

array(rsquoallowrsquo allow authenticated users to perform any action

rsquousersrsquo=gtarray(rsquorsquo)

)

array(rsquodenyrsquo deny all users

rsquousersrsquo=gtarray(rsquorsquo)

)

)

The above rules state that all users can access the index and view actions and authenti-cated users can access any actions including the admin action The user should be denied

24 3 Post Management

access in any other scenario Note that these rules are evaluated in the order they arelisted here The first rule matching the current context makes the access decision Forexample if the current user is the system owner who tries to visit the post creation pagethe second rule will match and it will give the access to the user

322 Customizing create and update Operations

The create and update operations are very similar They both need to display an HTMLform to collect user inputs validate them and save them into database The main differ-ence is that the update operation will pre-populate the form with the existing post datafound in the database For this reason the yiic tool generates a partial view wwwroot

blogprotectedviewspost formphp that is embedded in both the create and update

views to render the needed HTML form

We first change the formphp file so that the HTML form only collects the inputs wewant title content tags and status We use plain text fields to collect inputs for thefirst three attributes and a dropdown list to collect input for status The dropdown listoptions are the text displays of the possible post statuses

ltphp echo CHtmlactiveDropDownList($postrsquostatusrsquoLookupitems(rsquoPostStatusrsquo)) gt

In the above we call Lookupitems(rsquoPostStatusrsquo) to bring back the list of post statuses

We then modify the Post class so that it can automatically set some attributes (eg create

time author id) before a post is saved to the database We override the beforeSave()

method as follows

protected function beforeSave()

if(parentbeforeSave())

if($this-gtisNewRecord)

$this-gtcreate time=$this-gtupdate time=time()

$this-gtauthor id=Yiiapp()-gtuser-gtid

else

$this-gtupdate time=time()

return true

else

return false

33 Displaying Posts 25

When we save a post we want to update the tbl tag table to reflect the change of tagfrequencies We can do this work in the afterSave() method which is automaticallyinvoked by Yii after a post is successfully saved into the database

protected function afterSave()

parentafterSave()

Tagmodel()-gtupdateFrequency($this-gt oldTags $this-gttags)

private $ oldTags

protected function afterFind()

parentafterFind()

$this-gt oldTags=$this-gttags

In the implementation because we want to detect if the user changes the tags in case he isupdating an existing post we need to know what the old tags are For this reason we alsowrite the afterFind() method to keep the old tags in the variable oldTags The methodafterFind() is invoked automatically by Yii when an AR record is populated with thedata from database

We are not going to give details of the TagupdateFrequency() method here Interestedreaders may refer to the file wwwrootyiidemosblogprotectedmodelsTagphp

33 Displaying Posts

In our blog application a post may be displayed among a list of posts or by itself Theformer is implemented as the index operation while the latter the view operation In thissection we customize both operations to fulfill our initial requirements

331 Customizing view Operation

The view operation is implemented by the actionView() method in PostController Itsdisplay is generated by the view view with the view file wwwrootblogprotectedviews

postviewphp

Below is the relevant code implementing the view operation in PostController

public function actionView()

26 3 Post Management

$this-gtrender(rsquoviewrsquoarray(

$post=$this-gtloadModel()

$this-gtrender(rsquoviewrsquoarray(

rsquomodelrsquo=gt$post

))

))

private $ model

public function loadModel()

if($this-gt model===null)

if(isset($ GET[rsquoidrsquo]))

if(Yiiapp()-gtuser-gtisGuest)

$condition=rsquostatus=rsquoPostSTATUS PUBLISHED

rsquo OR status=rsquoPOSTSTATUS ARCHIVED

else

$condition=rsquorsquo

$this-gt model=Postmodel()-gtfindbyPk($ GET[rsquoidrsquo] $condition)

if($this-gt model===null)

throw new CHttpException(404rsquoThe requested page does not existrsquo)

return $this-gt model

Our change mainly lies in the loadModel() method In this method we query the Post

table according to the id GET parameter If the post is not found or if it is not publishedor archived (when the user is a guest) we will throw a 404 HTTP error Otherwise thepost object is returned to actionView() which in turn passes the post object to the viewscript for further display

Tip Yii captures HTTP exceptions (instances of CHttpException) and displaysthem in either predefined templates or customized error views The skeleton appli-cation generated by yiic already contains a customized error view in wwwroot

blogprotectedviewssiteerrorphp We can modify this file if we want tofurther customize the error display

The change in the view script is mainly about ajdusting the formatting and styles of thepost display We will not go into details here Interested readers may refer to wwwroot

blogprotectedviewspostviewphp

33 Displaying Posts 27

332 Customizing index Operation

Like the view operation we customize the index operation in two places the actionIndex()

method in PostController and the view file wwwrootblogprotectedviewspostindex

php We mainly need to add the support for displaying a list of posts that are associatedwith a specified tag

Below is the modified actionIndex() method in PostController

public function actionIndex()

$criteria=new CDbCriteria(array(

rsquoconditionrsquo=gtrsquostatus=rsquoPostSTATUS PUBLISHED

rsquoorderrsquo=gtrsquoupdate time DESCrsquo

rsquowithrsquo=gtrsquocommentCountrsquo

))

if(isset($ GET[rsquotagrsquo]))

$criteria-gtaddSearchCondition(rsquotagsrsquo$ GET[rsquotagrsquo])

$dataProvider=new CActiveDataProvider(rsquoPostrsquo array(

rsquopaginationrsquo=gtarray(

rsquopageSizersquo=gtYiiapp()-gtparams[rsquopostsPerPagersquo]

)

rsquocriteriarsquo=gt$criteria

))

$this-gtrender(rsquoindexrsquoarray(

rsquodataProviderrsquo=gt$dataProvider

))

In the above we first create a query criteria for retrieving post list The criteria statesthat only published posts should be returned and they should be sorted according to theirupdate time in descending order Because when displaying a post in the list we want toshow how many comments the post has received in the criteria we also specify to bringback commentCount which if you remember is a relation declared in Postrelations()

In case when a user wants to see posts with a specific tag we would add a search conditionto the criteria to look for the specified tag

Using the query criteria we create a data provider which mainly serves for three purposesFirst it does pagination of the data when too many results may be returned Herewe customize the pagination by setting the page size to be an application parameterpostsPerPage Second it does sorting according to the user request And finally it feedsthe paginated and sorted data to widgets or view code for presentation

28 3 Post Management

After we finish with actionIndex() we modify the index view as follows Our change ismainly about adding the h1 header when the user specifies to display posts with a tag

ltphp if(empty($ GET[rsquotagrsquo])) gt

lth1gtPosts Tagged with ltigtltphp echo CHtmlencode($ GET[rsquotagrsquo]) gtltigtlth1gt

ltphp endif gt

ltphp $this-gtwidget(rsquoziiwidgetsCListViewrsquo array(

rsquodataProviderrsquo=gt$dataProvider

rsquoitemViewrsquo=gtrsquo viewrsquo

rsquotemplatersquo=gtitemsnpager)) gt

Note that in the above we use [CListView] to display the post list This widget requires apartial view to display the detail of each individual post Here we specify the partial viewto be view which means the file wwwrootblogprotectedviewspost viewphp In thisview script we can acccess the post instance being displayed via a local variable named$data

34 Managing Posts

Managing posts mainly refers to listing posts in an administrative view that allows us tosee posts with all statuses updating them and deleting them They are accomplished bythe admin operation and the delete operation respectively The code generated by yiic

does not need much modification Below we mainly explain how these two operations areimplemented

341 Listing Posts in Tabular View

The admin operation shows posts with all statuses in a tabular view The view supportssorting and pagination The following is the actionAdmin() method in PostController

public function actionAdmin()

$dataProvider=new CActiveDataProvider(rsquoPostrsquo array(

rsquosortrsquo=gtarray(

rsquodefaultOrderrsquo=gtrsquostatus update time DESCrsquo

)

))

$this-gtrender(rsquoadminrsquoarray(

rsquodataProviderrsquo=gt$dataProvider

))

34 Managing Posts 29

The above code is very similar to that in actionIndex() The main difference is in thequery criteria Here we do not specify any particular query criteria since we want to seeall posts We do customize the sort property of the data provider so that by default theposts are shown in the order of their statuses and update time

Below is the code for the admin view

lth1gtManage Postslth1gt

ltphp $this-gtwidget(rsquoziiwidgetsgridCGridViewrsquo array(

rsquodataProviderrsquo=gt$dataProvider

rsquocolumnsrsquo=gtarray(

array(

rsquonamersquo=gtrsquotitlersquo

rsquotypersquo=gtrsquorawrsquo

rsquovaluersquo=gtrsquoCHtmllink(CHtmlencode($data-gttitle) $data-gturl)rsquo

)

array(

rsquonamersquo=gtrsquostatusrsquo

rsquovaluersquo=gtrsquoLookupitem(PostStatus$data-gtstatus)rsquo

)

rsquocreate timedatetimersquo

rsquoupdate timedatetimersquo

array(

rsquoclassrsquo=gtrsquoCButtonColumnrsquo

)

)

)) gt

We use [CGridView] to display the posts It allows us to sort by a column and paginatethrough the posts if there are too many to be displayed in a single page Our change ismainly about how to display each column For example for the title column we specifythat it should be displayed as a hyperlink that points to the detailed view of the post

Tip When displaying text we call CHtmlencode() to encode HTML entities init This prevents from cross-site scripting attack

342 Deleting Posts

In the admin data grid there is a delete button in each row Clicking on the button shoulddelete the corresponding post Internally this triggers the delete action implemented asfollows

30 3 Post Management

public function actionDelete()

if(Yiiapp()-gtrequest-gtisPostRequest)

we only allow deletion via POST request

$this-gtloadModel()-gtdelete()

if(isset($ POST[rsquoajaxrsquo]))

$this-gtredirect(array(rsquoindexrsquo))

else

throw new CHttpException(400rsquoInvalid request Please do not repeat this request againrsquo)

The above code is the one generated by the yiic tool without any change We wouldlike to explain a little bit more about the checking on $ POST[rsquoajaxrsquo] The [CGridView]widget has a very nice feature that its sorting pagination and deletion operations are alldone in AJAX mode by default That means the whole page does not get reloaded if anyof the above operations is performed However it is also possible that the widget runs innon-AJAX mode (by setting its ajaxUpdate property to be false or disabling JavaScript onthe client side) It is necessary for the delete action to differentiate these two scenarios ifthe delete request is made via AJAX we should not redirect the user browser otherwisewe should

Deleting a post should also cause the deletion of all comments for that post In additionwe should also update the tbl tag table regarding the tags for the deleted post Both ofthese tasks can be achieved by writing an afterDelete method in the Post model class asfollows

protected function afterDelete()

parentafterDelete()

Commentmodel()-gtdeleteAll(rsquopost id=rsquo$this-gtid)

Tagmodel()-gtupdateFrequency($this-gttags rsquorsquo)

The above code is very straightforward it first deletes all those comments whose post id

is the same as the ID of the deleted post it then updates the tbl tag table for the tags

of the deleted post

34 Managing Posts 31

Tip We have to explicitly delete all comments for the deleted post here becauseSQLite does not really support foreign key constraints In a DBMS that supportsthis constraint (such as MySQL PostgreSQL) the foreign key constraint can be setup such that the DBMS automatically deletes the related comments if the post isdeleted In that case we no longer this explicit deletion call in our code

32 3 Post Management

Chapter 4

Comment Management

41 Customizing Comment Model

For the Comment model we mainly need to customize the rules() and attributeLabels()

methods The attributeLabels() method returns a mapping between attribute namesand attribute labels We do not need to touch relations() since the code generated bythe yiic tool is good enough

411 Customizing rules() Method

We first customize the validation rules generated by the yiic tool The following rules areused for comments

public function rules()

return array(

array(rsquocontent author emailrsquo rsquorequiredrsquo)

array(rsquoauthor email urlrsquo rsquolengthrsquo rsquomaxrsquo=gt128)

array(rsquoemailrsquorsquoemailrsquo)

array(rsquourlrsquorsquourlrsquo)

)

In the above we specify that the author email and content attributes are required thelength of author email and url cannot exceed 128 the email attribute must be a validemail address and the url attribute must be a valid URL

412 Customizing attributeLabels() Method

We then customize the attributeLabels() method to declare the label display for eachmodel attribute This method returns an array consisting of name-label pairs When wecall CHtmlactiveLabel() to display an attribute label

34 4 Comment Management

public function attributeLabels()

return array(

rsquoidrsquo =gt rsquoIdrsquo

rsquocontentrsquo =gt rsquoCommentrsquo

rsquostatusrsquo =gt rsquoStatusrsquo

rsquocreate timersquo =gt rsquoCreate Timersquo

rsquoauthorrsquo =gt rsquoNamersquo

rsquoemailrsquo =gt rsquoEmailrsquo

rsquourlrsquo =gt rsquoWebsitersquo

rsquopost idrsquo =gt rsquoPostrsquo

)

Tip If the label for an attribute is not declared in attributeLabels() an algo-rithm will be used to generate an appropriate label For example a label VerifyCode will be generated for attributes verify code or verifyCode

413 Customizing Saving Process

Because we want to record the creation time of a comment we override the beforeSave()

method of Comment like we do for the Post model

protected function beforeSave()

if(parentbeforeSave())

if($this-gtisNewRecord)

$this-gtcreate time=time()

return true

else

return false

42 Creating and Displaying Comments

In this section we implement the comment display and creation features

421 Displaying Comments

Instead of displaying and creating comments on individual pages we use the post detailpage (generated by the view action of PostController) Below the post content display

42 Creating and Displaying Comments 35

we display first a list of comments belonging to that post and then a comment creationform

In order to display comments on the post detail page we modify the view script wwwrootblogprotectedviewspostviewphp as follows

ltdiv id=commentsgt

ltphp if($model-gtcommentCountgt=1) gt

lth3gt

ltphp echo $model-gtcommentCount rsquocomment(s)rsquo gt

lth3gt

ltphp $this-gtrenderPartial(rsquo commentsrsquoarray(

rsquopostrsquo=gt$model

rsquocommentsrsquo=gt$model-gtcomments

)) gt

ltphp endif gt

ltdivgt

In the above we call renderPartial() to render a partial view named comments to displaythe list of comments belonging to the current post Note that in the view we use theexpression $model-gtcomments to retrieve the comments for the post This is valid becausewe have declared a comments relation in the Post class Evaluating this expression wouldtrigger an implicit JOIN database query to bring back the proper comments This featureis known as lazy relational query

The partial view comments is not very interesting It mainly goes through every commentand displays the detail of it Interested readers may refer to wwwrootyiidemosblog

protectedpost commentsphp

422 Creating Comments

To handle comment creation we first modify the actionView() method of PostController

as follows

public function actionView()

$post=$this-gtloadModel()

$comment=$this-gtnewComment($post)

$this-gtrender(rsquoviewrsquoarray(

rsquomodelrsquo=gt$post

rsquocommentrsquo=gt$comment

))

36 4 Comment Management

protected function newComment($post)

$comment=new Comment

if(isset($ POST[rsquoCommentrsquo]))

$comment-gtattributes=$ POST[rsquoCommentrsquo]

if($post-gtaddComment($comment))

if($comment-gtstatus==CommentSTATUS PENDING)

Yiiapp()-gtuser-gtsetFlash(rsquocommentSubmittedrsquorsquoThank yoursquo)

$this-gtrefresh()

return $comment

In the above we call the newComment() method before we render view In the newComment()

method we generate a Comment instance and check if the comment form is submitted If sowe try to add the comment for the post by calling $post-gtaddComment($comment) If it goesthrough we refresh the post detail page In case the comment needs to be approved we willshow a flash message to indicate this decision A flash message is usually a confirmationmessage displayed to end users If the user clicks on the refresh button of his browser themessage will disappear

We also need to modify wwwrootblogprotectedviewspostviewphp furthermore

ltdiv id=commentsgt

lth3gtLeave a Commentlth3gt

ltphp if(Yiiapp()-gtuser-gthasFlash(rsquocommentSubmittedrsquo)) gt

ltdiv class=successgt

ltphp echo Yiiapp()-gtuser-gtgetFlash(rsquocommentSubmittedrsquo) gt

ltdivgt

ltphp else gt

ltphp $this-gtrenderPartial(rsquocomment formrsquoarray(

rsquomodelrsquo=gt$comment

)) gt

ltphp endif gt

ltdivgtlt-- comments --gt

43 Managing Comments 37

In the above code we display the flash message if it is available If not we displaythe comment input form by rendering the partial view wwwrootblogprotectedviews

comment formphp

43 Managing Comments

Comment management includes updating deleting and approving comments These op-erations are implemented as actions in the CommentController class

431 Updating and Deleting Comments

The code generated by yiic for updating and deleting comments remains largely un-changed

432 Approving Comments

When comments are newly created they are in pending approval status and need to beapproved in order to be visible to guest users Approving a comment is mainly aboutchanging the status column of the comment

We create an actionApprove() method in CommentController as follows

public function actionApprove()

if(Yiiapp()-gtrequest-gtisPostRequest)

$comment=$this-gtloadModel()

$comment-gtapprove()

$this-gtredirect(array(rsquoindexrsquo))

else

throw new CHttpException(400rsquoInvalid requestrsquo)

In the above when the approve action is invoked via a POST request we call the approve()

method defined in the Comment model to change the status We then redirect the userbrowser to the page displaying the post that this comment belongs to

We also modify the actionIndex() method of Comment to show all comments We wouldlike to see comments pending approved to show up first

public function actionIndex()

38 4 Comment Management

$dataProvider=new CActiveDataProvider(rsquoCommentrsquo array(

rsquocriteriarsquo=gtarray(

rsquowithrsquo=gtrsquopostrsquo

rsquoorderrsquo=gtrsquotstatus tcreate time DESCrsquo

)

))

$this-gtrender(rsquoindexrsquoarray(

rsquodataProviderrsquo=gt$dataProvider

))

Notice that in the above code because both tbl post and tbl comment have columns statusand create time we need to disambiguate the corresponding column reference by prefixingthem with table alias names As described in the guide the alias for the primary table ina relational query is always t Therefore we are prefixing t to the status and create time

columns in the above code

Like the post index view the index view for CommentController uses [CListView] to displaythe comment list which in turn uses the partial view wwwrootblogprotectedviews

comment viewphp to display the detail of each individual comment We will not go intodetails here Interested readers may refer to the corresponding file in the blog demowwwrootyiidemosblogprotectedviewscomment viewphp

Chapter 5

Portlets

51 Customizing Page Layout

Features like rdquothe most recent commentsrdquo rdquotag cloudrdquo are better to be implemented inportlets A portlet is a pluggable user interface component that renders a fragment ofHTML code In this section we describe how to set up the portlet architecture for ourblog application

Before we go into details about developing portlets we first need to adjust our page layoutso that the portlets can be placed in a side bar

A page layout determines how to share common sections (eg header footer) amongdifferent pages and how to embed contents that are specific for different pages In Yiia page layout is represented as a view under the protectedviewslayouts folder Whenwe call the render() method in a controller the rendering result will be automaticallyembedded into this layout view before it is displayed to end users The default layoutview is named as main which means the wwwrootblogprotectedviewslayoutsmain

php file in our blog application

We could directly modify the main layout to add a side bar However doing so wouldmake some irrelevant pages such as about contact pages to have a side bar which is notalways desirable For this reason we need different layouts for different pages

We thus create two different layouts column1 and column2 The former displays the pagein a single column while the latter displays two columns in the page one of which is theside bar We will use column2 layout for the post and comment pages and column1 forthe rest of the pages Because both column1 and column2 still share the same header andfooter sections we let them to share the main layout

Based on the above description we create the column1 layout as follows

ltphp $this-gtbeginContent(rsquolayoutsmainrsquo) gt

ltdiv class=containergt

40 5 Portlets

ltdiv id=contentgt

ltphp echo $content gt

ltdivgtlt-- content --gt

ltdivgt

ltphp $this-gtendContent() gt

and the column2 layout as follows

ltphp $this-gtbeginContent(rsquolayoutsmainrsquo) gt

ltdiv class=containergt

ltdiv class=span-18gt

ltdiv id=contentgt

ltphp echo $content gt

ltdivgtlt-- content --gt

ltdivgt

ltdiv class=span-6 lastgt

ltdiv id=sidebargt

portlets to be placed here

ltdivgtlt-- sidebar --gt

ltdivgt

ltdivgt

ltphp $this-gtendContent() gt

As we can see both column1 and column2 are enclosed in a pair of beginContent andendContent calls They essentially use the specified view layoutsmain to decorate theenclosed content In all these layout views the variable $content refers to the dynamiccontent to be embedded into the layout

Besides customizing the layout view file we also need to adjust the CSS file wwwroot

blogcssmaincss so that the overall appearance would look like what we see in the blogdemo We will not go into details here

Info The skeleton application generated by the yiic tool uses the BluePrint CSSframework It provides a nice font reset and a robust grid system For more detailson how to use this framework please refer to its documentation

After creating the new layouts we need to set the layout property of controllers to makeuse of them So far we have three controllers SiteController PostController andCommentController We set SiteController to use column1 as the default layout whilethe rest two of the controllers use column2 for example

class PostController extends Controller

52 Creating User Menu Portlet 41

public $layout=rsquocolumn2rsquo

Tip If some view of the controller does not use the same layout as declaredby the layout property it may customize it in the view by a simple assignment$this-gtlayout=rsquonewlayoutrsquo

52 Creating User Menu Portlet

Based on the requirements analysis we need three different portlets the rdquouser menurdquoportlet the rdquotag cloudrdquo portlet and the rdquorecent commentsrdquo portlet We will develop theseportlets by extending the [CPortlet] widget provided by Yii

In this section we will develop our first concrete portlet - the user menu portlet whichdisplays a list of menu items that are only available to authenticated users The menucontains four items

bull Approve Comments a hyperlink that leads to a list of comments pending approval

bull Create New Post a hyperlink that leads to the post creation page

bull Manage Posts a hyperlink that leads to the post management page

bull Logout a link button that would log out the current user

521 Creating UserMenu Class

We create the UserMenu class to represent the logic part of the user menu portlet Theclass is saved in the file wwwrootblogprotectedcomponentsUserMenuphp which has thefollowing content

Yiiimport(rsquoziiwidgetsCPortletrsquo)

class UserMenu extends CPortlet

public function init()

$this-gttitle=CHtmlencode(Yiiapp()-gtuser-gtname)

parentinit()

42 5 Portlets

protected function renderContent()

$this-gtrender(rsquouserMenursquo)

The UserMenu class extends from the CPortlet class that we created previously It overridesboth the init() method and the renderContent() method of CPortlet The former setsthe portlet title to be the name of the current user the latter generates the portlet bodycontent by rendering a view named userMenu

Tip Notice that we have to explicitly include the CPortlet class by calling Yii

import() before we refer to it the first time This is because CPortlet is part of thezii project ndash the official extension library for Yii For performance considerationclasses in this project are not listed as core classes Therefore we have to import itbefore we use it the first time

522 Creating userMenu View

Next we create the userMenu view which is saved in the file wwwrootblogprotected

componentsviewsuserMenuphp

ltulgt

ltligtltphp echo CHtmllink(rsquoCreate New Postrsquoarray(rsquopostcreatersquo)) gtltligt

ltligtltphp echo CHtmllink(rsquoManage Postsrsquoarray(rsquopostadminrsquo)) gtltligt

ltligtltphp echo CHtmllink(rsquoApprove Commentsrsquoarray(rsquocommentindexrsquo))

rsquo (rsquo Commentmodel()-gtpendingCommentCount rsquo)rsquo gtltligt

ltligtltphp echo CHtmllink(rsquoLogoutrsquoarray(rsquositelogoutrsquo)) gtltligt

ltulgt

Info By default view files for a widget should be placed under the views sub-directory of the directory containing the widget class file The file name must bethe same as the view name

523 Using UserMenu Portlet

It is time for us to make use of our newly completed UserMenu portlet We modify thelayout view file wwwrootblogprotectedviewslayoutscolumn2php as follows

53 Creating Tag Cloud Portlet 43

ltdiv id=sidebargt

ltphp if(Yiiapp()-gtuser-gtisGuest) $this-gtwidget(rsquoUserMenursquo) gt

ltdivgt

In the above we call the widget() method to generate and execute an instance of theUserMenu class Because the portlet should only be displayed to authenticated users weonly call widget() when the isGuest property of the current user is false (meaning theuser is authenticated)

524 Testing UserMenu Portlet

Letrsquos test what we have so far

1 Open a browser window and enter the URL httpwwwexamplecomblogindex

php Verify that there is nothing displayed in the side bar section of the page

2 Click on the Login hyperlink and fill out the login form to login If successful verifythat the UserMenu portlet appears in the side bar and the portlet has the usernameas its title

3 Click on the rsquoLogoutrsquo hyperlink in the UserMenu portlet Verify that the logout actionis successful and the UserMenu portlet disappears

525 Summary

What we have created is a portlet that is highly reusable We can easily reuse it in adifferent project with little or no modification Moreover the design of this portlet followsclosely the philosophy that logic and presentation should be separated While we did notpoint this out in the previous sections such practice is used nearly everywhere in a typicalYii application

53 Creating Tag Cloud Portlet

Tag cloud displays a list of post tags with visual decorations hinting the popularity of eachindividual tag

531 Creating TagCloud Class

We create the TagCloud class in the file wwwrootblogprotectedcomponentsTagCloud

php The file has the following content

44 5 Portlets

Yiiimport(rsquoziiwidgetsCPortletrsquo)

class TagCloud extends CPortlet

public $title=rsquoTagsrsquo

public $maxTags=20

protected function renderContent()

$tags=Tagmodel()-gtfindTagWeights($this-gtmaxTags)

foreach($tags as $tag=gt$weight)

$link=CHtmllink(CHtmlencode($tag) array(rsquopostindexrsquorsquotagrsquo=gt$tag))

echo CHtmltag(rsquospanrsquo array(

rsquoclassrsquo=gtrsquotagrsquo

rsquostylersquo=gtfont-size$weightpt) $link)n

Unlike the UserMenu portlet the TagCloud portlet does not use a view Instead its presen-tation is done in the renderContent() method This is because the presentation does notcontain much HTML tags

We display each tag as a hyperlink to the post index page with the corresponding tagparameter The font size of each tag link is adjusted according to their relative weightamong other tags If a tag has higher frequency value than the other it will have a biggerfont size

532 Using TagCloud Portlet

Usage of the TagCloud portlet is very simple We modify the layout file wwwrootblog

protectedviewslayoutsmainphp as follows

ltdiv id=sidebargt

ltphp if(Yiiapp()-gtuser-gtisGuest) $this-gtwidget(rsquoUserMenursquo) gt

ltphp $this-gtwidget(rsquoTagCloudrsquo array(

rsquomaxTagsrsquo=gtYiiapp()-gtparams[rsquotagCloudCountrsquo]

)) gt

ltdivgt

54 Creating Recent Comments Portlet 45

54 Creating Recent Comments Portlet

In this section we create the last portlet that displays a list of comments recently pub-lished

541 Creating RecentComments Class

We create the RecentComments class in the file wwwrootblogprotectedcomponentsRecentCommentsphp The file has the following content

Yiiimport(rsquoziiwidgetsCPortletrsquo)

class RecentComments extends CPortlet

public $title=rsquoRecent Commentsrsquo

public $maxComments=10

public function getRecentComments()

return Commentmodel()-gtfindRecentComments($this-gtmaxComments)

protected function renderContent()

$this-gtrender(rsquorecentCommentsrsquo)

In the above we invoke the findRecentComments method which is defined in the Comment

class as follows

class Comment extends CActiveRecord

public function findRecentComments($limit=10)

return $this-gtwith(rsquopostrsquo)-gtfindAll(array(

rsquoconditionrsquo=gtrsquotstatus=rsquoselfSTATUS APPROVED

rsquoorderrsquo=gtrsquotcreate time DESCrsquo

rsquolimitrsquo=gt$limit

))

46 5 Portlets

542 Creating recentComments View

The recentComments view is saved in the file wwwrootblogprotectedcomponentsviews

recentCommentsphp It simply displays every comment returned by the RecentComments

getRecentComments() method

543 Using RecentComments Portlet

We modify the layout file wwwrootblogprotectedviewslayoutsmainphp to embedthis last portlet

ltdiv id=sidebargt

ltphp if(Yiiapp()-gtuser-gtisGuest) $this-gtwidget(rsquoUserMenursquo) gt

ltphp $this-gtwidget(rsquoTagCloudrsquo array(

rsquomaxTagsrsquo=gtYiiapp()-gtparams[rsquotagCloudCountrsquo]

)) gt

ltphp $this-gtwidget(rsquoRecentCommentsrsquo array(

rsquomaxCommentsrsquo=gtYiiapp()-gtparams[rsquorecentCommentCountrsquo]

)) gt

ltdivgt

Chapter 6

Final Work

61 Beautifying URLs

The URLs linking various pages of our blog application currently look ugly For examplethe URL for the page showing a post looks like the following

indexphpr=postshowampid=1

In this section we describe how to beautify these URLs and make them SEO-friendlyOur goal is to be able to use the following URLs in the application

1 indexphppostsyii leads to the page showing a list of posts with tag yii

2 indexphppost2A+Test+Post leads to the page showing the detail of the postwith ID 2 whose title is A Test Post

3 indexphppostupdate1 leads to the page that allows updating the post with ID1

Note that in the second URL format we include the post title in the URL This is mainlyto make the URL SEO friendly It is said that search engines may also respect the wordsfound in a URL when it is being indexed

To achieve our goal we modify the application configuration as follows

return array(

rsquocomponentsrsquo=gtarray(

rsquourlManagerrsquo=gtarray(

rsquourlFormatrsquo=gtrsquopathrsquo

rsquorulesrsquo=gtarray(

48 6 Final Work

rsquopostltidd+gtlttitlegtrsquo=gtrsquopostviewrsquorsquopostslttaggtrsquo=gtrsquopostindexrsquo

)

)

)

)

In the above we configure the urlManager component by setting its urlFormat propertyto be path and adding a set of rules

The rules are used by urlManager to parse and create the URLs in the desired formatFor example the first rule says that if a URL indexphppostsyii is requested theurlManager component should be responsible to dispatch the request to the route post

index and generate a tag GET parameter with the value yii On the other hand whencreating a URL with the route postindex and parameter tag the urlManager componentwill also use this rule to generate the desired URL indexphppostsyii For this reasonwe say that urlManager is a two-way URL manager

The urlManager component can further beautify our URLs such as hiding indexphp inthe URLs appending suffix like html to the URLs We can obtain these features easilyby configuring various properties of urlManager in the application configuration For moredetails please refer to the Guide

62 Logging Errors

A production Web application often needs sophisticated logging for various events In ourblog application we would like to log the errors occurring when it is being used Sucherrors could be programming mistakes or usersrsquo misuse of the system Logging these errorswill help us to improve the blog application

We enable the error logging by modifying the application configuration as follows

return array(

rsquopreloadrsquo=gtarray(rsquologrsquo)

rsquocomponentsrsquo=gtarray(

rsquologrsquo=gtarray(

rsquoclassrsquo=gtrsquoCLogRouterrsquo

rsquoroutesrsquo=gtarray(

array(

rsquoclassrsquo=gtrsquoCFileLogRoutersquo

rsquolevelsrsquo=gtrsquoerror warningrsquo

63 Final Tune-up and Deployment 49

)

)

)

)

)

With the above configuration if an error or warning occurs detailed information will belogged and saved in a file located under the directory wwwrootblogprotectedruntime

The log component offers more advanced features such as sending log messages to a listof email addresses displaying log messages in JavaScript console window etc For moredetails please refer to the Guide

63 Final Tune-up and Deployment

We are close to finish our blog application Before deployment we would like to do sometune-ups

631 Changing Home Page

We change to use the post list page as the home page We modify the application config-uration as follows

return array(

rsquodefaultControllerrsquo=gtrsquopostrsquo

)

Tip Because PostController already declares list to be its default action whenwe access the home page of the application we will see the result generated by thelist action of the post controller

632 Enabling Schema Caching

Because ActiveRecord relies on the metadata about tables to determine the column infor-mation it takes time to read the metadata and analyze it This may not be a problemduring development stage but for an application running in production mode it is a totalwaste of time if the database schema does not change Therefore we should enable theschema caching by modifying the application configuration as follows

50 6 Final Work

return array(

rsquocomponentsrsquo=gtarray(

rsquocachersquo=gtarray(

rsquoclassrsquo=gtrsquoCDbCachersquo

)

rsquodbrsquo=gtarray(

rsquoclassrsquo=gtrsquosystemdbCDbConnectionrsquo

rsquoconnectionStringrsquo=gtrsquosqlitewwwrootblogprotecteddatablogdbrsquo

rsquoschemaCachingDurationrsquo=gt3600

)

)

)

In the above we first add a cache component which uses a default SQLite database as thecaching storage If our server is equipped with other caching extensions such as APCwe could change to use them as well We also modify the db component by setting itsschemaCachingDuration property to be 3600 which means the parsed database schemadata can remain valid in cache for 3600 seconds

633 Disabling Debugging Mode

We modify the entry script file wwwrootblogindexphp by removing the line definingthe constant YII DEBUG This constant is useful during development stage because it allowsYii to display more debugging information when an error occurs However when theapplication is running in production mode displaying debugging information is not agood idea because it may contain sensitive information such as where the script file islocated and the content in the file etc

634 Deploying the Application

The final deployment process manly involves copying the directory wwwrootblog to thetarget directory The following checklist shows every needed step

1 Install Yii in the target place if it is not available

2 Copy the entire directory wwwrootblog to the target place

3 Edit the entry script file indexphp by pointing the $yii variable to the new Yiibootstrap file

4 Edit the file protectedyiicphp by setting the $yiic variable to be the new Yiiyiicphp file

64 Future Enhancements 51

5 Change the permission of the directories assets and protectedruntime so that theyare writable by the Web server process

64 Future Enhancements

641 Using a Theme

Without writing any code our blog application is already themeable To use a themewe mainly need to develop the theme by writing customized view files in the theme Forexample to use a theme named classic that uses a different page layout we would createa layout view file wwwrootblogthemesclassicviewslayoutsmainphp We also needto change the application configuration to indicate our choice of the classic theme

return array(

rsquothemersquo=gtrsquoclassicrsquo

)

642 Internationalization

We may also internationalize our blog application so that its pages can be displayed indifferent languages This mainly involves efforts in two aspects

First we may create view files in different languages For example for the list pageof PostController we can create a view file wwwrootblogprotectedviewspostzh cn

listphp When the application is configured to use simplified Chinese (the language codeis zh cn) Yii will automatically use this new view file instead of the original one

Second we may create message translations for those messages generated by code Themessage translations should be saved as files under the directory wwwrootblogprotected

messages We also need to modify the code where we use text strings by enclosing themin the method call Yiit()

For more details about internationalization please refer to the Guide

643 Improving Performance with Cache

While the Yii framework itself is very efficient it is not necessarily true that an applicationwritten in Yii efficient There are several places in our blog application that we can improvethe performance For example the tag clould portlet could be one of the performancebottlenecks because it involves complex database query and PHP logic

52 6 Final Work

We can make use of the sophisticated caching feature provided by Yii to improve theperformance One of the most useful components in Yii is COutputCache which caches afragment of page display so that the underlying code generating the fragment does not needto be executed for every request For example in the layout file wwwrootblogprotected

viewslayoutsmainphp we can enclose the tag cloud portlet with COutputCache

ltphp if($this-gtbeginCache(rsquotagCloudrsquo array(rsquodurationrsquo=gt3600))) gt

ltphp $this-gtwidget(rsquoTagCloudrsquo array(

rsquomaxTagsrsquo=gtYiiapp()-gtparams[rsquotagCloudCountrsquo]

)) gt

ltphp $this-gtendCache() gt

With the above code the tag cloud display will be served from cache instead of beinggenerated on-the-fly for every request The cached content will remain valid in cache for3600 seconds

644 Adding New Features

Our blog application only has very basic functionalities To become a complete blogsystem more features are needed for example calendar portlet email notifications postcategorization archived post portlet and so on We will leave the implementation of thesefeatures to interested readers

  • Contents
  • License
  • Getting Started
    • Building a Blog System using Yii
    • Testdriving with Yii
      • Installing Yii
      • Creating Skeleton Application
      • Application Workflow
        • Requirements Analysis
        • Overall Design
          • Initial Prototyping
            • Setting Up Database
              • Creating Database
              • Establishing Database Connection
                • Scaffolding
                • Authenticating User
                • Summary
                  • Post Management
                    • Customizing Post Model
                      • Customizing rules() Method
                      • Customizing relations() Method
                      • Representing Status in Text
                        • Creating and Updating Posts
                          • Customizing Access Control
                          • Customizing create and update Operations
                            • Displaying Posts
                              • Customizing view Operation
                              • Customizing index Operation
                                • Managing Posts
                                  • Listing Posts in Tabular View
                                  • Deleting Posts
                                      • Comment Management
                                        • Customizing Comment Model
                                          • Customizing rules() Method
                                          • Customizing attributeLabels() Method
                                          • Customizing Saving Process
                                            • Creating and Displaying Comments
                                              • Displaying Comments
                                              • Creating Comments
                                                • Managing Comments
                                                  • Updating and Deleting Comments
                                                  • Approving Comments
                                                      • Portlets
                                                        • Customizing Page Layout
                                                        • Creating User Menu Portlet
                                                          • Creating UserMenu Class
                                                          • Creating userMenu View
                                                          • Using UserMenu Portlet
                                                          • Testing UserMenu Portlet
                                                          • Summary
                                                            • Creating Tag Cloud Portlet
                                                              • Creating TagCloud Class
                                                              • Using TagCloud Portlet
                                                                • Creating Recent Comments Portlet
                                                                  • Creating RecentComments Class
                                                                  • Creating recentComments View
                                                                  • Using RecentComments Portlet
                                                                      • Final Work
                                                                        • Beautifying URLs
                                                                        • Logging Errors
                                                                        • Final Tune-up and Deployment
                                                                          • Changing Home Page
                                                                          • Enabling Schema Caching
                                                                          • Disabling Debugging Mode
                                                                          • Deploying the Application
                                                                            • Future Enhancements
                                                                              • Using a Theme
                                                                              • Internationalization
                                                                              • Improving Performance with Cache
                                                                              • Adding New Features
Page 23: Yii Blog 1.1.0 Demo Tutor

16 2 Initial Prototyping

public function hashPassword($password$salt)

return md5($salt$password)

In the UserIdentity class we also override the getId() method which returns the id valueof the user found in the User table The parent implementation would return the usernameinstead Both the username and id properties will be stored in the user session and maybe accessed via Yiiapp()-gtuser from anywhere in our code

Tip In the UserIdentity class we reference the class CUserIdentity without ex-plicitly including the corresponding class file This is because CUserIdentity is acore class provided by the Yii framework Yii will automatically include the classfile for any core class when it is referenced for the first time

We also do the same with the User class This is because the User class file is placedunder the directory wwwrootblogprotectedmodels which has been added tothe PHP include path according to the following lines found in the applicationconfiguration

return array(

rsquoimportrsquo=gtarray(

rsquoapplicationmodelsrsquo

rsquoapplicationcomponentsrsquo

)

)

The above configuration says that any class whose class file is located under either wwwrootblogprotectedmodels or wwwrootblogprotectedcomponents willbe automatically included when the class is referenced for the first time

The UserIdentity class is mainly used by the LoginForm class to authenticate a user basedon the username and password input collected from the login page The following codefragment shows how UserIdentity is used

$identity=new UserIdentity($username$password)

$identity-gtauthenticate()

switch($identity-gterrorCode)

24 Summary 17

case UserIdentityERROR NONE

Yiiapp()-gtuser-gtlogin($identity)

break

Info People often get confused about identity and the user application componentThe former represents a way of performing authentication while the latter is usedto represent the information related with the current user An application can onlyhave one user component but it can have one or several identity classes dependingon what kind of authentication it supports Once authenticated an identity instancemay pass its state information to the user component so that they are globallyaccessible via user

To test the modified UserIdentity class we can browse the URL httpwwwexample

comblogindexphp and try logging in with the username and password that we store inthe User table If we use the database provided by the blog demo we should be able tologin with username demo and password demo Note that this blog system does not providethe user management feature As a result a user cannot change his account or create anew one through the Web interface The user management feature may be considered asa future enhancement to the blog application

24 Summary

We have completed the milestone 1 Letrsquos summarize what we have done so far

1 We identified the requirements to be fulfilled

2 We installed the Yii framework

3 We created a skeleton application

4 We designed and created the blog database

5 We modified the application configuration by adding the database connection

6 We generated the code that implements the basic CRUD operations for both postsand comments

7 We modified the authentication method to check against the tbl user table

18 2 Initial Prototyping

For a new project most of the time will be spent in step 1 and 4 for this first milestone

Although the code generated by the yiic tool implements fully functional CRUD oper-ations for a database table it often needs to be modified in practical applications Forthis reason in the next two milestone our job is to customize the generated CRUD codeabout posts and comments so that it reaches our initial requirements

In general we first modify the model class file by adding appropriate validation rules anddeclaring relational objects We then modify the controller action and view code for eachindividual CRUD operation

Chapter 3

Post Management

31 Customizing Post Model

The Post model class generated by the yiic tool mainly needs to be modified in two places

bull the rules() method specifies the validation rules for the model attributes

bull the relations() method specifies the related objects

Info A model consists of a list of attributes each associated with a column in thecorresponding database table Attributes can be declared explicitly as class membervariables or implicitly without any declaration

311 Customizing rules() Method

We first specify the validation rules which ensure the attribute values entered by usersare correct before they are saved to the database For example the status attribute ofPost should be an integer 1 2 or 3 The yiic tool also generates validation rules for eachmodel However these rules are based on the table column information and may not beappropriate

Based on the requirement analysis we modify the rules() method as follows

public function rules()

return array(

array(rsquotitle content statusrsquo rsquorequiredrsquo)

array(rsquotitlersquo rsquolengthrsquo rsquomaxrsquo=gt128)

array(rsquostatusrsquo rsquoinrsquo rsquorangersquo=gtarray(123))

array(rsquotagsrsquo rsquomatchrsquo rsquopatternrsquo=gtrsquo^[ws]+$rsquorsquomessagersquo=gtrsquoTags can only contain word charactersrsquo)

20 3 Post Management

array(rsquotagsrsquo rsquonormalizeTagsrsquo)

)

In the above we specify that the title content and status attributes are required thelength of title should not exceed 128 the status attribute value should be 1 (draft) 2(published) or 3 (archived) and the tags attribute should only contain word charactersand commas In addition we use normalizeTags to normalize the user-entered tags so thatthe tags are unique and properly separated with commas

The validators such as required length in and match are all built-in validators providedby Yii The normalizeTags validator is a method-based validator that we need to definein the Post class For more information about how to specify validation rules please referto the Guide

public function normalizeTags($attribute$params)

$this-gttags=Tagarray2string(array unique(Tagstring2array($this-gttags)))

The rules declared in this method are executed one by one when we call the validate() orsave() method of the model instance

Note It is very important to remember that attributes appearing in rules() mustbe those to be entered by end users Other attributes such as id and create time

in the Post model which are set by our code or database should not be in rules()For more details please refer to Securing Attribute Assignments

After making these changes we can visit the post creation page again to verify that thenew validation rules are taking effect

312 Customizing relations() Method

Lastly we customize the relations() method to specify the related objects of a post Bydeclaring these related objects in relations() we can exploit the powerful RelationalActiveRecord (RAR) feature to access the related object information of a post such asits author and comments without the need to write complex SQL JOIN statements

We customize the relations() method as follows

31 Customizing Post Model 21

public function relations()

return array(

rsquoauthorrsquo =gt array(selfBELONGS TO rsquoUserrsquo rsquoauthor idrsquo)

rsquocommentsrsquo =gt array(selfHAS MANY rsquoCommentrsquo rsquopost idrsquo

rsquoconditionrsquo=gtrsquocommentsstatus=rsquoCommentSTATUS APPROVED

rsquoorderrsquo=gtrsquocommentscreate time DESCrsquo)

rsquocommentCountrsquo =gt array(selfSTAT rsquoCommentrsquo rsquopost idrsquo

rsquoconditionrsquo=gtrsquostatus=rsquoCommentSTATUS APPROVED)

)

The above relations state that

bull A post belongs to an author whose class is User and the relationship is establishedbased on the author id attribute value of the post

bull A post has many comments whose class is Comment and the relationship is establishedbased on the post id attribute value of the comments These comments should besorted according to their creation time and the comments must be approved

bull The commentCount relation is a bit special as it returns back an aggregation resultwhich is about how many comments the post has

With the above relation declaration we can easily access the author and comments of apost like the following

$author=$post-gtauthor

echo $author-gtusername

$comments=$post-gtcomments

foreach($comments as $comment)

echo $comment-gtcontent

For more details about how to declare and use relations please refer to the Guide

313 Representing Status in Text

Because the status of a post is stored as an integer in the database we need to provide atextual representation so that it is more intuitive when being displayed to end users In alarge system the similar requirement is very common

22 3 Post Management

As a generic solution we use the tbl lookup table to store the mapping between integervalues and textual representations that are needed by other data objects We modify theLookup model class as follows to more easily access the textual data in the table

class Lookup extends CActiveRecord

private static $ items=array()

public static function items($type)

if(isset(self$ items[$type]))

selfloadItems($type)

return self$ items[$type]

public static function item($type$code)

if(isset(self$ items[$type]))

selfloadItems($type)

return isset(self$ items[$type][$code]) self$ items[$type][$code] false

private static function loadItems($type)

self$ items[$type]=array()

$models=selfmodel()-gtfindAll(array(

rsquoconditionrsquo=gtrsquotype=typersquo

rsquoparamsrsquo=gtarray(rsquotypersquo=gt$type)

rsquoorderrsquo=gtrsquopositionrsquo

))

foreach($models as $model)

self$ items[$type][$model-gtcode]=$model-gtname

Our new code mainly provides two static methods Lookupitems() and Lookupitem()The former returns a list of strings belonging to the specified data type while the latterreturns a particular string for the given data type and data value

Our blog database is pre-populated with two lookup types PostStatus and CommentStatusThe former refers to the possible post statuses while the latter the comment statuses

In order to make our code easier to read we also declare a set of constants to representthe status integer values We should use these constants through our code when referringto the corresponding status values

32 Creating and Updating Posts 23

class Post extends CActiveRecord

const STATUS DRAFT=0

const STATUS PUBLISHED=1

const STATUS ARCHIVED=2

Therefore we can call Lookupitems(rsquoPostStatusrsquo) to get the list of possible post statuses(text strings indexed by the corresponding integer values) and call Lookupitem(rsquoPostStatusrsquoPostSTATUS PUBLISHED) to get the string representation of the published status

32 Creating and Updating Posts

With the Post model ready we need to fine-tune the actions and views for the controllerPostController In this section we first customize the access control of CRUD operationswe then modify the code implementing the create and update operations

321 Customizing Access Control

The first thing we want to do is to customize the access control because the code generatedby yiic does not fit our needs

We modify the accessRules() method in the file wwwrootblogprotectedcontrollers

PostControllerphp as follows

public function accessRules()

return array(

array(rsquoallowrsquo allow all users to perform rsquolistrsquo and rsquoshowrsquo actions

rsquoactionsrsquo=gtarray(rsquoindexrsquo rsquoviewrsquo)

rsquousersrsquo=gtarray(rsquorsquo)

)

array(rsquoallowrsquo allow authenticated users to perform any action

rsquousersrsquo=gtarray(rsquorsquo)

)

array(rsquodenyrsquo deny all users

rsquousersrsquo=gtarray(rsquorsquo)

)

)

The above rules state that all users can access the index and view actions and authenti-cated users can access any actions including the admin action The user should be denied

24 3 Post Management

access in any other scenario Note that these rules are evaluated in the order they arelisted here The first rule matching the current context makes the access decision Forexample if the current user is the system owner who tries to visit the post creation pagethe second rule will match and it will give the access to the user

322 Customizing create and update Operations

The create and update operations are very similar They both need to display an HTMLform to collect user inputs validate them and save them into database The main differ-ence is that the update operation will pre-populate the form with the existing post datafound in the database For this reason the yiic tool generates a partial view wwwroot

blogprotectedviewspost formphp that is embedded in both the create and update

views to render the needed HTML form

We first change the formphp file so that the HTML form only collects the inputs wewant title content tags and status We use plain text fields to collect inputs for thefirst three attributes and a dropdown list to collect input for status The dropdown listoptions are the text displays of the possible post statuses

ltphp echo CHtmlactiveDropDownList($postrsquostatusrsquoLookupitems(rsquoPostStatusrsquo)) gt

In the above we call Lookupitems(rsquoPostStatusrsquo) to bring back the list of post statuses

We then modify the Post class so that it can automatically set some attributes (eg create

time author id) before a post is saved to the database We override the beforeSave()

method as follows

protected function beforeSave()

if(parentbeforeSave())

if($this-gtisNewRecord)

$this-gtcreate time=$this-gtupdate time=time()

$this-gtauthor id=Yiiapp()-gtuser-gtid

else

$this-gtupdate time=time()

return true

else

return false

33 Displaying Posts 25

When we save a post we want to update the tbl tag table to reflect the change of tagfrequencies We can do this work in the afterSave() method which is automaticallyinvoked by Yii after a post is successfully saved into the database

protected function afterSave()

parentafterSave()

Tagmodel()-gtupdateFrequency($this-gt oldTags $this-gttags)

private $ oldTags

protected function afterFind()

parentafterFind()

$this-gt oldTags=$this-gttags

In the implementation because we want to detect if the user changes the tags in case he isupdating an existing post we need to know what the old tags are For this reason we alsowrite the afterFind() method to keep the old tags in the variable oldTags The methodafterFind() is invoked automatically by Yii when an AR record is populated with thedata from database

We are not going to give details of the TagupdateFrequency() method here Interestedreaders may refer to the file wwwrootyiidemosblogprotectedmodelsTagphp

33 Displaying Posts

In our blog application a post may be displayed among a list of posts or by itself Theformer is implemented as the index operation while the latter the view operation In thissection we customize both operations to fulfill our initial requirements

331 Customizing view Operation

The view operation is implemented by the actionView() method in PostController Itsdisplay is generated by the view view with the view file wwwrootblogprotectedviews

postviewphp

Below is the relevant code implementing the view operation in PostController

public function actionView()

26 3 Post Management

$this-gtrender(rsquoviewrsquoarray(

$post=$this-gtloadModel()

$this-gtrender(rsquoviewrsquoarray(

rsquomodelrsquo=gt$post

))

))

private $ model

public function loadModel()

if($this-gt model===null)

if(isset($ GET[rsquoidrsquo]))

if(Yiiapp()-gtuser-gtisGuest)

$condition=rsquostatus=rsquoPostSTATUS PUBLISHED

rsquo OR status=rsquoPOSTSTATUS ARCHIVED

else

$condition=rsquorsquo

$this-gt model=Postmodel()-gtfindbyPk($ GET[rsquoidrsquo] $condition)

if($this-gt model===null)

throw new CHttpException(404rsquoThe requested page does not existrsquo)

return $this-gt model

Our change mainly lies in the loadModel() method In this method we query the Post

table according to the id GET parameter If the post is not found or if it is not publishedor archived (when the user is a guest) we will throw a 404 HTTP error Otherwise thepost object is returned to actionView() which in turn passes the post object to the viewscript for further display

Tip Yii captures HTTP exceptions (instances of CHttpException) and displaysthem in either predefined templates or customized error views The skeleton appli-cation generated by yiic already contains a customized error view in wwwroot

blogprotectedviewssiteerrorphp We can modify this file if we want tofurther customize the error display

The change in the view script is mainly about ajdusting the formatting and styles of thepost display We will not go into details here Interested readers may refer to wwwroot

blogprotectedviewspostviewphp

33 Displaying Posts 27

332 Customizing index Operation

Like the view operation we customize the index operation in two places the actionIndex()

method in PostController and the view file wwwrootblogprotectedviewspostindex

php We mainly need to add the support for displaying a list of posts that are associatedwith a specified tag

Below is the modified actionIndex() method in PostController

public function actionIndex()

$criteria=new CDbCriteria(array(

rsquoconditionrsquo=gtrsquostatus=rsquoPostSTATUS PUBLISHED

rsquoorderrsquo=gtrsquoupdate time DESCrsquo

rsquowithrsquo=gtrsquocommentCountrsquo

))

if(isset($ GET[rsquotagrsquo]))

$criteria-gtaddSearchCondition(rsquotagsrsquo$ GET[rsquotagrsquo])

$dataProvider=new CActiveDataProvider(rsquoPostrsquo array(

rsquopaginationrsquo=gtarray(

rsquopageSizersquo=gtYiiapp()-gtparams[rsquopostsPerPagersquo]

)

rsquocriteriarsquo=gt$criteria

))

$this-gtrender(rsquoindexrsquoarray(

rsquodataProviderrsquo=gt$dataProvider

))

In the above we first create a query criteria for retrieving post list The criteria statesthat only published posts should be returned and they should be sorted according to theirupdate time in descending order Because when displaying a post in the list we want toshow how many comments the post has received in the criteria we also specify to bringback commentCount which if you remember is a relation declared in Postrelations()

In case when a user wants to see posts with a specific tag we would add a search conditionto the criteria to look for the specified tag

Using the query criteria we create a data provider which mainly serves for three purposesFirst it does pagination of the data when too many results may be returned Herewe customize the pagination by setting the page size to be an application parameterpostsPerPage Second it does sorting according to the user request And finally it feedsthe paginated and sorted data to widgets or view code for presentation

28 3 Post Management

After we finish with actionIndex() we modify the index view as follows Our change ismainly about adding the h1 header when the user specifies to display posts with a tag

ltphp if(empty($ GET[rsquotagrsquo])) gt

lth1gtPosts Tagged with ltigtltphp echo CHtmlencode($ GET[rsquotagrsquo]) gtltigtlth1gt

ltphp endif gt

ltphp $this-gtwidget(rsquoziiwidgetsCListViewrsquo array(

rsquodataProviderrsquo=gt$dataProvider

rsquoitemViewrsquo=gtrsquo viewrsquo

rsquotemplatersquo=gtitemsnpager)) gt

Note that in the above we use [CListView] to display the post list This widget requires apartial view to display the detail of each individual post Here we specify the partial viewto be view which means the file wwwrootblogprotectedviewspost viewphp In thisview script we can acccess the post instance being displayed via a local variable named$data

34 Managing Posts

Managing posts mainly refers to listing posts in an administrative view that allows us tosee posts with all statuses updating them and deleting them They are accomplished bythe admin operation and the delete operation respectively The code generated by yiic

does not need much modification Below we mainly explain how these two operations areimplemented

341 Listing Posts in Tabular View

The admin operation shows posts with all statuses in a tabular view The view supportssorting and pagination The following is the actionAdmin() method in PostController

public function actionAdmin()

$dataProvider=new CActiveDataProvider(rsquoPostrsquo array(

rsquosortrsquo=gtarray(

rsquodefaultOrderrsquo=gtrsquostatus update time DESCrsquo

)

))

$this-gtrender(rsquoadminrsquoarray(

rsquodataProviderrsquo=gt$dataProvider

))

34 Managing Posts 29

The above code is very similar to that in actionIndex() The main difference is in thequery criteria Here we do not specify any particular query criteria since we want to seeall posts We do customize the sort property of the data provider so that by default theposts are shown in the order of their statuses and update time

Below is the code for the admin view

lth1gtManage Postslth1gt

ltphp $this-gtwidget(rsquoziiwidgetsgridCGridViewrsquo array(

rsquodataProviderrsquo=gt$dataProvider

rsquocolumnsrsquo=gtarray(

array(

rsquonamersquo=gtrsquotitlersquo

rsquotypersquo=gtrsquorawrsquo

rsquovaluersquo=gtrsquoCHtmllink(CHtmlencode($data-gttitle) $data-gturl)rsquo

)

array(

rsquonamersquo=gtrsquostatusrsquo

rsquovaluersquo=gtrsquoLookupitem(PostStatus$data-gtstatus)rsquo

)

rsquocreate timedatetimersquo

rsquoupdate timedatetimersquo

array(

rsquoclassrsquo=gtrsquoCButtonColumnrsquo

)

)

)) gt

We use [CGridView] to display the posts It allows us to sort by a column and paginatethrough the posts if there are too many to be displayed in a single page Our change ismainly about how to display each column For example for the title column we specifythat it should be displayed as a hyperlink that points to the detailed view of the post

Tip When displaying text we call CHtmlencode() to encode HTML entities init This prevents from cross-site scripting attack

342 Deleting Posts

In the admin data grid there is a delete button in each row Clicking on the button shoulddelete the corresponding post Internally this triggers the delete action implemented asfollows

30 3 Post Management

public function actionDelete()

if(Yiiapp()-gtrequest-gtisPostRequest)

we only allow deletion via POST request

$this-gtloadModel()-gtdelete()

if(isset($ POST[rsquoajaxrsquo]))

$this-gtredirect(array(rsquoindexrsquo))

else

throw new CHttpException(400rsquoInvalid request Please do not repeat this request againrsquo)

The above code is the one generated by the yiic tool without any change We wouldlike to explain a little bit more about the checking on $ POST[rsquoajaxrsquo] The [CGridView]widget has a very nice feature that its sorting pagination and deletion operations are alldone in AJAX mode by default That means the whole page does not get reloaded if anyof the above operations is performed However it is also possible that the widget runs innon-AJAX mode (by setting its ajaxUpdate property to be false or disabling JavaScript onthe client side) It is necessary for the delete action to differentiate these two scenarios ifthe delete request is made via AJAX we should not redirect the user browser otherwisewe should

Deleting a post should also cause the deletion of all comments for that post In additionwe should also update the tbl tag table regarding the tags for the deleted post Both ofthese tasks can be achieved by writing an afterDelete method in the Post model class asfollows

protected function afterDelete()

parentafterDelete()

Commentmodel()-gtdeleteAll(rsquopost id=rsquo$this-gtid)

Tagmodel()-gtupdateFrequency($this-gttags rsquorsquo)

The above code is very straightforward it first deletes all those comments whose post id

is the same as the ID of the deleted post it then updates the tbl tag table for the tags

of the deleted post

34 Managing Posts 31

Tip We have to explicitly delete all comments for the deleted post here becauseSQLite does not really support foreign key constraints In a DBMS that supportsthis constraint (such as MySQL PostgreSQL) the foreign key constraint can be setup such that the DBMS automatically deletes the related comments if the post isdeleted In that case we no longer this explicit deletion call in our code

32 3 Post Management

Chapter 4

Comment Management

41 Customizing Comment Model

For the Comment model we mainly need to customize the rules() and attributeLabels()

methods The attributeLabels() method returns a mapping between attribute namesand attribute labels We do not need to touch relations() since the code generated bythe yiic tool is good enough

411 Customizing rules() Method

We first customize the validation rules generated by the yiic tool The following rules areused for comments

public function rules()

return array(

array(rsquocontent author emailrsquo rsquorequiredrsquo)

array(rsquoauthor email urlrsquo rsquolengthrsquo rsquomaxrsquo=gt128)

array(rsquoemailrsquorsquoemailrsquo)

array(rsquourlrsquorsquourlrsquo)

)

In the above we specify that the author email and content attributes are required thelength of author email and url cannot exceed 128 the email attribute must be a validemail address and the url attribute must be a valid URL

412 Customizing attributeLabels() Method

We then customize the attributeLabels() method to declare the label display for eachmodel attribute This method returns an array consisting of name-label pairs When wecall CHtmlactiveLabel() to display an attribute label

34 4 Comment Management

public function attributeLabels()

return array(

rsquoidrsquo =gt rsquoIdrsquo

rsquocontentrsquo =gt rsquoCommentrsquo

rsquostatusrsquo =gt rsquoStatusrsquo

rsquocreate timersquo =gt rsquoCreate Timersquo

rsquoauthorrsquo =gt rsquoNamersquo

rsquoemailrsquo =gt rsquoEmailrsquo

rsquourlrsquo =gt rsquoWebsitersquo

rsquopost idrsquo =gt rsquoPostrsquo

)

Tip If the label for an attribute is not declared in attributeLabels() an algo-rithm will be used to generate an appropriate label For example a label VerifyCode will be generated for attributes verify code or verifyCode

413 Customizing Saving Process

Because we want to record the creation time of a comment we override the beforeSave()

method of Comment like we do for the Post model

protected function beforeSave()

if(parentbeforeSave())

if($this-gtisNewRecord)

$this-gtcreate time=time()

return true

else

return false

42 Creating and Displaying Comments

In this section we implement the comment display and creation features

421 Displaying Comments

Instead of displaying and creating comments on individual pages we use the post detailpage (generated by the view action of PostController) Below the post content display

42 Creating and Displaying Comments 35

we display first a list of comments belonging to that post and then a comment creationform

In order to display comments on the post detail page we modify the view script wwwrootblogprotectedviewspostviewphp as follows

ltdiv id=commentsgt

ltphp if($model-gtcommentCountgt=1) gt

lth3gt

ltphp echo $model-gtcommentCount rsquocomment(s)rsquo gt

lth3gt

ltphp $this-gtrenderPartial(rsquo commentsrsquoarray(

rsquopostrsquo=gt$model

rsquocommentsrsquo=gt$model-gtcomments

)) gt

ltphp endif gt

ltdivgt

In the above we call renderPartial() to render a partial view named comments to displaythe list of comments belonging to the current post Note that in the view we use theexpression $model-gtcomments to retrieve the comments for the post This is valid becausewe have declared a comments relation in the Post class Evaluating this expression wouldtrigger an implicit JOIN database query to bring back the proper comments This featureis known as lazy relational query

The partial view comments is not very interesting It mainly goes through every commentand displays the detail of it Interested readers may refer to wwwrootyiidemosblog

protectedpost commentsphp

422 Creating Comments

To handle comment creation we first modify the actionView() method of PostController

as follows

public function actionView()

$post=$this-gtloadModel()

$comment=$this-gtnewComment($post)

$this-gtrender(rsquoviewrsquoarray(

rsquomodelrsquo=gt$post

rsquocommentrsquo=gt$comment

))

36 4 Comment Management

protected function newComment($post)

$comment=new Comment

if(isset($ POST[rsquoCommentrsquo]))

$comment-gtattributes=$ POST[rsquoCommentrsquo]

if($post-gtaddComment($comment))

if($comment-gtstatus==CommentSTATUS PENDING)

Yiiapp()-gtuser-gtsetFlash(rsquocommentSubmittedrsquorsquoThank yoursquo)

$this-gtrefresh()

return $comment

In the above we call the newComment() method before we render view In the newComment()

method we generate a Comment instance and check if the comment form is submitted If sowe try to add the comment for the post by calling $post-gtaddComment($comment) If it goesthrough we refresh the post detail page In case the comment needs to be approved we willshow a flash message to indicate this decision A flash message is usually a confirmationmessage displayed to end users If the user clicks on the refresh button of his browser themessage will disappear

We also need to modify wwwrootblogprotectedviewspostviewphp furthermore

ltdiv id=commentsgt

lth3gtLeave a Commentlth3gt

ltphp if(Yiiapp()-gtuser-gthasFlash(rsquocommentSubmittedrsquo)) gt

ltdiv class=successgt

ltphp echo Yiiapp()-gtuser-gtgetFlash(rsquocommentSubmittedrsquo) gt

ltdivgt

ltphp else gt

ltphp $this-gtrenderPartial(rsquocomment formrsquoarray(

rsquomodelrsquo=gt$comment

)) gt

ltphp endif gt

ltdivgtlt-- comments --gt

43 Managing Comments 37

In the above code we display the flash message if it is available If not we displaythe comment input form by rendering the partial view wwwrootblogprotectedviews

comment formphp

43 Managing Comments

Comment management includes updating deleting and approving comments These op-erations are implemented as actions in the CommentController class

431 Updating and Deleting Comments

The code generated by yiic for updating and deleting comments remains largely un-changed

432 Approving Comments

When comments are newly created they are in pending approval status and need to beapproved in order to be visible to guest users Approving a comment is mainly aboutchanging the status column of the comment

We create an actionApprove() method in CommentController as follows

public function actionApprove()

if(Yiiapp()-gtrequest-gtisPostRequest)

$comment=$this-gtloadModel()

$comment-gtapprove()

$this-gtredirect(array(rsquoindexrsquo))

else

throw new CHttpException(400rsquoInvalid requestrsquo)

In the above when the approve action is invoked via a POST request we call the approve()

method defined in the Comment model to change the status We then redirect the userbrowser to the page displaying the post that this comment belongs to

We also modify the actionIndex() method of Comment to show all comments We wouldlike to see comments pending approved to show up first

public function actionIndex()

38 4 Comment Management

$dataProvider=new CActiveDataProvider(rsquoCommentrsquo array(

rsquocriteriarsquo=gtarray(

rsquowithrsquo=gtrsquopostrsquo

rsquoorderrsquo=gtrsquotstatus tcreate time DESCrsquo

)

))

$this-gtrender(rsquoindexrsquoarray(

rsquodataProviderrsquo=gt$dataProvider

))

Notice that in the above code because both tbl post and tbl comment have columns statusand create time we need to disambiguate the corresponding column reference by prefixingthem with table alias names As described in the guide the alias for the primary table ina relational query is always t Therefore we are prefixing t to the status and create time

columns in the above code

Like the post index view the index view for CommentController uses [CListView] to displaythe comment list which in turn uses the partial view wwwrootblogprotectedviews

comment viewphp to display the detail of each individual comment We will not go intodetails here Interested readers may refer to the corresponding file in the blog demowwwrootyiidemosblogprotectedviewscomment viewphp

Chapter 5

Portlets

51 Customizing Page Layout

Features like rdquothe most recent commentsrdquo rdquotag cloudrdquo are better to be implemented inportlets A portlet is a pluggable user interface component that renders a fragment ofHTML code In this section we describe how to set up the portlet architecture for ourblog application

Before we go into details about developing portlets we first need to adjust our page layoutso that the portlets can be placed in a side bar

A page layout determines how to share common sections (eg header footer) amongdifferent pages and how to embed contents that are specific for different pages In Yiia page layout is represented as a view under the protectedviewslayouts folder Whenwe call the render() method in a controller the rendering result will be automaticallyembedded into this layout view before it is displayed to end users The default layoutview is named as main which means the wwwrootblogprotectedviewslayoutsmain

php file in our blog application

We could directly modify the main layout to add a side bar However doing so wouldmake some irrelevant pages such as about contact pages to have a side bar which is notalways desirable For this reason we need different layouts for different pages

We thus create two different layouts column1 and column2 The former displays the pagein a single column while the latter displays two columns in the page one of which is theside bar We will use column2 layout for the post and comment pages and column1 forthe rest of the pages Because both column1 and column2 still share the same header andfooter sections we let them to share the main layout

Based on the above description we create the column1 layout as follows

ltphp $this-gtbeginContent(rsquolayoutsmainrsquo) gt

ltdiv class=containergt

40 5 Portlets

ltdiv id=contentgt

ltphp echo $content gt

ltdivgtlt-- content --gt

ltdivgt

ltphp $this-gtendContent() gt

and the column2 layout as follows

ltphp $this-gtbeginContent(rsquolayoutsmainrsquo) gt

ltdiv class=containergt

ltdiv class=span-18gt

ltdiv id=contentgt

ltphp echo $content gt

ltdivgtlt-- content --gt

ltdivgt

ltdiv class=span-6 lastgt

ltdiv id=sidebargt

portlets to be placed here

ltdivgtlt-- sidebar --gt

ltdivgt

ltdivgt

ltphp $this-gtendContent() gt

As we can see both column1 and column2 are enclosed in a pair of beginContent andendContent calls They essentially use the specified view layoutsmain to decorate theenclosed content In all these layout views the variable $content refers to the dynamiccontent to be embedded into the layout

Besides customizing the layout view file we also need to adjust the CSS file wwwroot

blogcssmaincss so that the overall appearance would look like what we see in the blogdemo We will not go into details here

Info The skeleton application generated by the yiic tool uses the BluePrint CSSframework It provides a nice font reset and a robust grid system For more detailson how to use this framework please refer to its documentation

After creating the new layouts we need to set the layout property of controllers to makeuse of them So far we have three controllers SiteController PostController andCommentController We set SiteController to use column1 as the default layout whilethe rest two of the controllers use column2 for example

class PostController extends Controller

52 Creating User Menu Portlet 41

public $layout=rsquocolumn2rsquo

Tip If some view of the controller does not use the same layout as declaredby the layout property it may customize it in the view by a simple assignment$this-gtlayout=rsquonewlayoutrsquo

52 Creating User Menu Portlet

Based on the requirements analysis we need three different portlets the rdquouser menurdquoportlet the rdquotag cloudrdquo portlet and the rdquorecent commentsrdquo portlet We will develop theseportlets by extending the [CPortlet] widget provided by Yii

In this section we will develop our first concrete portlet - the user menu portlet whichdisplays a list of menu items that are only available to authenticated users The menucontains four items

bull Approve Comments a hyperlink that leads to a list of comments pending approval

bull Create New Post a hyperlink that leads to the post creation page

bull Manage Posts a hyperlink that leads to the post management page

bull Logout a link button that would log out the current user

521 Creating UserMenu Class

We create the UserMenu class to represent the logic part of the user menu portlet Theclass is saved in the file wwwrootblogprotectedcomponentsUserMenuphp which has thefollowing content

Yiiimport(rsquoziiwidgetsCPortletrsquo)

class UserMenu extends CPortlet

public function init()

$this-gttitle=CHtmlencode(Yiiapp()-gtuser-gtname)

parentinit()

42 5 Portlets

protected function renderContent()

$this-gtrender(rsquouserMenursquo)

The UserMenu class extends from the CPortlet class that we created previously It overridesboth the init() method and the renderContent() method of CPortlet The former setsthe portlet title to be the name of the current user the latter generates the portlet bodycontent by rendering a view named userMenu

Tip Notice that we have to explicitly include the CPortlet class by calling Yii

import() before we refer to it the first time This is because CPortlet is part of thezii project ndash the official extension library for Yii For performance considerationclasses in this project are not listed as core classes Therefore we have to import itbefore we use it the first time

522 Creating userMenu View

Next we create the userMenu view which is saved in the file wwwrootblogprotected

componentsviewsuserMenuphp

ltulgt

ltligtltphp echo CHtmllink(rsquoCreate New Postrsquoarray(rsquopostcreatersquo)) gtltligt

ltligtltphp echo CHtmllink(rsquoManage Postsrsquoarray(rsquopostadminrsquo)) gtltligt

ltligtltphp echo CHtmllink(rsquoApprove Commentsrsquoarray(rsquocommentindexrsquo))

rsquo (rsquo Commentmodel()-gtpendingCommentCount rsquo)rsquo gtltligt

ltligtltphp echo CHtmllink(rsquoLogoutrsquoarray(rsquositelogoutrsquo)) gtltligt

ltulgt

Info By default view files for a widget should be placed under the views sub-directory of the directory containing the widget class file The file name must bethe same as the view name

523 Using UserMenu Portlet

It is time for us to make use of our newly completed UserMenu portlet We modify thelayout view file wwwrootblogprotectedviewslayoutscolumn2php as follows

53 Creating Tag Cloud Portlet 43

ltdiv id=sidebargt

ltphp if(Yiiapp()-gtuser-gtisGuest) $this-gtwidget(rsquoUserMenursquo) gt

ltdivgt

In the above we call the widget() method to generate and execute an instance of theUserMenu class Because the portlet should only be displayed to authenticated users weonly call widget() when the isGuest property of the current user is false (meaning theuser is authenticated)

524 Testing UserMenu Portlet

Letrsquos test what we have so far

1 Open a browser window and enter the URL httpwwwexamplecomblogindex

php Verify that there is nothing displayed in the side bar section of the page

2 Click on the Login hyperlink and fill out the login form to login If successful verifythat the UserMenu portlet appears in the side bar and the portlet has the usernameas its title

3 Click on the rsquoLogoutrsquo hyperlink in the UserMenu portlet Verify that the logout actionis successful and the UserMenu portlet disappears

525 Summary

What we have created is a portlet that is highly reusable We can easily reuse it in adifferent project with little or no modification Moreover the design of this portlet followsclosely the philosophy that logic and presentation should be separated While we did notpoint this out in the previous sections such practice is used nearly everywhere in a typicalYii application

53 Creating Tag Cloud Portlet

Tag cloud displays a list of post tags with visual decorations hinting the popularity of eachindividual tag

531 Creating TagCloud Class

We create the TagCloud class in the file wwwrootblogprotectedcomponentsTagCloud

php The file has the following content

44 5 Portlets

Yiiimport(rsquoziiwidgetsCPortletrsquo)

class TagCloud extends CPortlet

public $title=rsquoTagsrsquo

public $maxTags=20

protected function renderContent()

$tags=Tagmodel()-gtfindTagWeights($this-gtmaxTags)

foreach($tags as $tag=gt$weight)

$link=CHtmllink(CHtmlencode($tag) array(rsquopostindexrsquorsquotagrsquo=gt$tag))

echo CHtmltag(rsquospanrsquo array(

rsquoclassrsquo=gtrsquotagrsquo

rsquostylersquo=gtfont-size$weightpt) $link)n

Unlike the UserMenu portlet the TagCloud portlet does not use a view Instead its presen-tation is done in the renderContent() method This is because the presentation does notcontain much HTML tags

We display each tag as a hyperlink to the post index page with the corresponding tagparameter The font size of each tag link is adjusted according to their relative weightamong other tags If a tag has higher frequency value than the other it will have a biggerfont size

532 Using TagCloud Portlet

Usage of the TagCloud portlet is very simple We modify the layout file wwwrootblog

protectedviewslayoutsmainphp as follows

ltdiv id=sidebargt

ltphp if(Yiiapp()-gtuser-gtisGuest) $this-gtwidget(rsquoUserMenursquo) gt

ltphp $this-gtwidget(rsquoTagCloudrsquo array(

rsquomaxTagsrsquo=gtYiiapp()-gtparams[rsquotagCloudCountrsquo]

)) gt

ltdivgt

54 Creating Recent Comments Portlet 45

54 Creating Recent Comments Portlet

In this section we create the last portlet that displays a list of comments recently pub-lished

541 Creating RecentComments Class

We create the RecentComments class in the file wwwrootblogprotectedcomponentsRecentCommentsphp The file has the following content

Yiiimport(rsquoziiwidgetsCPortletrsquo)

class RecentComments extends CPortlet

public $title=rsquoRecent Commentsrsquo

public $maxComments=10

public function getRecentComments()

return Commentmodel()-gtfindRecentComments($this-gtmaxComments)

protected function renderContent()

$this-gtrender(rsquorecentCommentsrsquo)

In the above we invoke the findRecentComments method which is defined in the Comment

class as follows

class Comment extends CActiveRecord

public function findRecentComments($limit=10)

return $this-gtwith(rsquopostrsquo)-gtfindAll(array(

rsquoconditionrsquo=gtrsquotstatus=rsquoselfSTATUS APPROVED

rsquoorderrsquo=gtrsquotcreate time DESCrsquo

rsquolimitrsquo=gt$limit

))

46 5 Portlets

542 Creating recentComments View

The recentComments view is saved in the file wwwrootblogprotectedcomponentsviews

recentCommentsphp It simply displays every comment returned by the RecentComments

getRecentComments() method

543 Using RecentComments Portlet

We modify the layout file wwwrootblogprotectedviewslayoutsmainphp to embedthis last portlet

ltdiv id=sidebargt

ltphp if(Yiiapp()-gtuser-gtisGuest) $this-gtwidget(rsquoUserMenursquo) gt

ltphp $this-gtwidget(rsquoTagCloudrsquo array(

rsquomaxTagsrsquo=gtYiiapp()-gtparams[rsquotagCloudCountrsquo]

)) gt

ltphp $this-gtwidget(rsquoRecentCommentsrsquo array(

rsquomaxCommentsrsquo=gtYiiapp()-gtparams[rsquorecentCommentCountrsquo]

)) gt

ltdivgt

Chapter 6

Final Work

61 Beautifying URLs

The URLs linking various pages of our blog application currently look ugly For examplethe URL for the page showing a post looks like the following

indexphpr=postshowampid=1

In this section we describe how to beautify these URLs and make them SEO-friendlyOur goal is to be able to use the following URLs in the application

1 indexphppostsyii leads to the page showing a list of posts with tag yii

2 indexphppost2A+Test+Post leads to the page showing the detail of the postwith ID 2 whose title is A Test Post

3 indexphppostupdate1 leads to the page that allows updating the post with ID1

Note that in the second URL format we include the post title in the URL This is mainlyto make the URL SEO friendly It is said that search engines may also respect the wordsfound in a URL when it is being indexed

To achieve our goal we modify the application configuration as follows

return array(

rsquocomponentsrsquo=gtarray(

rsquourlManagerrsquo=gtarray(

rsquourlFormatrsquo=gtrsquopathrsquo

rsquorulesrsquo=gtarray(

48 6 Final Work

rsquopostltidd+gtlttitlegtrsquo=gtrsquopostviewrsquorsquopostslttaggtrsquo=gtrsquopostindexrsquo

)

)

)

)

In the above we configure the urlManager component by setting its urlFormat propertyto be path and adding a set of rules

The rules are used by urlManager to parse and create the URLs in the desired formatFor example the first rule says that if a URL indexphppostsyii is requested theurlManager component should be responsible to dispatch the request to the route post

index and generate a tag GET parameter with the value yii On the other hand whencreating a URL with the route postindex and parameter tag the urlManager componentwill also use this rule to generate the desired URL indexphppostsyii For this reasonwe say that urlManager is a two-way URL manager

The urlManager component can further beautify our URLs such as hiding indexphp inthe URLs appending suffix like html to the URLs We can obtain these features easilyby configuring various properties of urlManager in the application configuration For moredetails please refer to the Guide

62 Logging Errors

A production Web application often needs sophisticated logging for various events In ourblog application we would like to log the errors occurring when it is being used Sucherrors could be programming mistakes or usersrsquo misuse of the system Logging these errorswill help us to improve the blog application

We enable the error logging by modifying the application configuration as follows

return array(

rsquopreloadrsquo=gtarray(rsquologrsquo)

rsquocomponentsrsquo=gtarray(

rsquologrsquo=gtarray(

rsquoclassrsquo=gtrsquoCLogRouterrsquo

rsquoroutesrsquo=gtarray(

array(

rsquoclassrsquo=gtrsquoCFileLogRoutersquo

rsquolevelsrsquo=gtrsquoerror warningrsquo

63 Final Tune-up and Deployment 49

)

)

)

)

)

With the above configuration if an error or warning occurs detailed information will belogged and saved in a file located under the directory wwwrootblogprotectedruntime

The log component offers more advanced features such as sending log messages to a listof email addresses displaying log messages in JavaScript console window etc For moredetails please refer to the Guide

63 Final Tune-up and Deployment

We are close to finish our blog application Before deployment we would like to do sometune-ups

631 Changing Home Page

We change to use the post list page as the home page We modify the application config-uration as follows

return array(

rsquodefaultControllerrsquo=gtrsquopostrsquo

)

Tip Because PostController already declares list to be its default action whenwe access the home page of the application we will see the result generated by thelist action of the post controller

632 Enabling Schema Caching

Because ActiveRecord relies on the metadata about tables to determine the column infor-mation it takes time to read the metadata and analyze it This may not be a problemduring development stage but for an application running in production mode it is a totalwaste of time if the database schema does not change Therefore we should enable theschema caching by modifying the application configuration as follows

50 6 Final Work

return array(

rsquocomponentsrsquo=gtarray(

rsquocachersquo=gtarray(

rsquoclassrsquo=gtrsquoCDbCachersquo

)

rsquodbrsquo=gtarray(

rsquoclassrsquo=gtrsquosystemdbCDbConnectionrsquo

rsquoconnectionStringrsquo=gtrsquosqlitewwwrootblogprotecteddatablogdbrsquo

rsquoschemaCachingDurationrsquo=gt3600

)

)

)

In the above we first add a cache component which uses a default SQLite database as thecaching storage If our server is equipped with other caching extensions such as APCwe could change to use them as well We also modify the db component by setting itsschemaCachingDuration property to be 3600 which means the parsed database schemadata can remain valid in cache for 3600 seconds

633 Disabling Debugging Mode

We modify the entry script file wwwrootblogindexphp by removing the line definingthe constant YII DEBUG This constant is useful during development stage because it allowsYii to display more debugging information when an error occurs However when theapplication is running in production mode displaying debugging information is not agood idea because it may contain sensitive information such as where the script file islocated and the content in the file etc

634 Deploying the Application

The final deployment process manly involves copying the directory wwwrootblog to thetarget directory The following checklist shows every needed step

1 Install Yii in the target place if it is not available

2 Copy the entire directory wwwrootblog to the target place

3 Edit the entry script file indexphp by pointing the $yii variable to the new Yiibootstrap file

4 Edit the file protectedyiicphp by setting the $yiic variable to be the new Yiiyiicphp file

64 Future Enhancements 51

5 Change the permission of the directories assets and protectedruntime so that theyare writable by the Web server process

64 Future Enhancements

641 Using a Theme

Without writing any code our blog application is already themeable To use a themewe mainly need to develop the theme by writing customized view files in the theme Forexample to use a theme named classic that uses a different page layout we would createa layout view file wwwrootblogthemesclassicviewslayoutsmainphp We also needto change the application configuration to indicate our choice of the classic theme

return array(

rsquothemersquo=gtrsquoclassicrsquo

)

642 Internationalization

We may also internationalize our blog application so that its pages can be displayed indifferent languages This mainly involves efforts in two aspects

First we may create view files in different languages For example for the list pageof PostController we can create a view file wwwrootblogprotectedviewspostzh cn

listphp When the application is configured to use simplified Chinese (the language codeis zh cn) Yii will automatically use this new view file instead of the original one

Second we may create message translations for those messages generated by code Themessage translations should be saved as files under the directory wwwrootblogprotected

messages We also need to modify the code where we use text strings by enclosing themin the method call Yiit()

For more details about internationalization please refer to the Guide

643 Improving Performance with Cache

While the Yii framework itself is very efficient it is not necessarily true that an applicationwritten in Yii efficient There are several places in our blog application that we can improvethe performance For example the tag clould portlet could be one of the performancebottlenecks because it involves complex database query and PHP logic

52 6 Final Work

We can make use of the sophisticated caching feature provided by Yii to improve theperformance One of the most useful components in Yii is COutputCache which caches afragment of page display so that the underlying code generating the fragment does not needto be executed for every request For example in the layout file wwwrootblogprotected

viewslayoutsmainphp we can enclose the tag cloud portlet with COutputCache

ltphp if($this-gtbeginCache(rsquotagCloudrsquo array(rsquodurationrsquo=gt3600))) gt

ltphp $this-gtwidget(rsquoTagCloudrsquo array(

rsquomaxTagsrsquo=gtYiiapp()-gtparams[rsquotagCloudCountrsquo]

)) gt

ltphp $this-gtendCache() gt

With the above code the tag cloud display will be served from cache instead of beinggenerated on-the-fly for every request The cached content will remain valid in cache for3600 seconds

644 Adding New Features

Our blog application only has very basic functionalities To become a complete blogsystem more features are needed for example calendar portlet email notifications postcategorization archived post portlet and so on We will leave the implementation of thesefeatures to interested readers

  • Contents
  • License
  • Getting Started
    • Building a Blog System using Yii
    • Testdriving with Yii
      • Installing Yii
      • Creating Skeleton Application
      • Application Workflow
        • Requirements Analysis
        • Overall Design
          • Initial Prototyping
            • Setting Up Database
              • Creating Database
              • Establishing Database Connection
                • Scaffolding
                • Authenticating User
                • Summary
                  • Post Management
                    • Customizing Post Model
                      • Customizing rules() Method
                      • Customizing relations() Method
                      • Representing Status in Text
                        • Creating and Updating Posts
                          • Customizing Access Control
                          • Customizing create and update Operations
                            • Displaying Posts
                              • Customizing view Operation
                              • Customizing index Operation
                                • Managing Posts
                                  • Listing Posts in Tabular View
                                  • Deleting Posts
                                      • Comment Management
                                        • Customizing Comment Model
                                          • Customizing rules() Method
                                          • Customizing attributeLabels() Method
                                          • Customizing Saving Process
                                            • Creating and Displaying Comments
                                              • Displaying Comments
                                              • Creating Comments
                                                • Managing Comments
                                                  • Updating and Deleting Comments
                                                  • Approving Comments
                                                      • Portlets
                                                        • Customizing Page Layout
                                                        • Creating User Menu Portlet
                                                          • Creating UserMenu Class
                                                          • Creating userMenu View
                                                          • Using UserMenu Portlet
                                                          • Testing UserMenu Portlet
                                                          • Summary
                                                            • Creating Tag Cloud Portlet
                                                              • Creating TagCloud Class
                                                              • Using TagCloud Portlet
                                                                • Creating Recent Comments Portlet
                                                                  • Creating RecentComments Class
                                                                  • Creating recentComments View
                                                                  • Using RecentComments Portlet
                                                                      • Final Work
                                                                        • Beautifying URLs
                                                                        • Logging Errors
                                                                        • Final Tune-up and Deployment
                                                                          • Changing Home Page
                                                                          • Enabling Schema Caching
                                                                          • Disabling Debugging Mode
                                                                          • Deploying the Application
                                                                            • Future Enhancements
                                                                              • Using a Theme
                                                                              • Internationalization
                                                                              • Improving Performance with Cache
                                                                              • Adding New Features
Page 24: Yii Blog 1.1.0 Demo Tutor

24 Summary 17

case UserIdentityERROR NONE

Yiiapp()-gtuser-gtlogin($identity)

break

Info People often get confused about identity and the user application componentThe former represents a way of performing authentication while the latter is usedto represent the information related with the current user An application can onlyhave one user component but it can have one or several identity classes dependingon what kind of authentication it supports Once authenticated an identity instancemay pass its state information to the user component so that they are globallyaccessible via user

To test the modified UserIdentity class we can browse the URL httpwwwexample

comblogindexphp and try logging in with the username and password that we store inthe User table If we use the database provided by the blog demo we should be able tologin with username demo and password demo Note that this blog system does not providethe user management feature As a result a user cannot change his account or create anew one through the Web interface The user management feature may be considered asa future enhancement to the blog application

24 Summary

We have completed the milestone 1 Letrsquos summarize what we have done so far

1 We identified the requirements to be fulfilled

2 We installed the Yii framework

3 We created a skeleton application

4 We designed and created the blog database

5 We modified the application configuration by adding the database connection

6 We generated the code that implements the basic CRUD operations for both postsand comments

7 We modified the authentication method to check against the tbl user table

18 2 Initial Prototyping

For a new project most of the time will be spent in step 1 and 4 for this first milestone

Although the code generated by the yiic tool implements fully functional CRUD oper-ations for a database table it often needs to be modified in practical applications Forthis reason in the next two milestone our job is to customize the generated CRUD codeabout posts and comments so that it reaches our initial requirements

In general we first modify the model class file by adding appropriate validation rules anddeclaring relational objects We then modify the controller action and view code for eachindividual CRUD operation

Chapter 3

Post Management

31 Customizing Post Model

The Post model class generated by the yiic tool mainly needs to be modified in two places

bull the rules() method specifies the validation rules for the model attributes

bull the relations() method specifies the related objects

Info A model consists of a list of attributes each associated with a column in thecorresponding database table Attributes can be declared explicitly as class membervariables or implicitly without any declaration

311 Customizing rules() Method

We first specify the validation rules which ensure the attribute values entered by usersare correct before they are saved to the database For example the status attribute ofPost should be an integer 1 2 or 3 The yiic tool also generates validation rules for eachmodel However these rules are based on the table column information and may not beappropriate

Based on the requirement analysis we modify the rules() method as follows

public function rules()

return array(

array(rsquotitle content statusrsquo rsquorequiredrsquo)

array(rsquotitlersquo rsquolengthrsquo rsquomaxrsquo=gt128)

array(rsquostatusrsquo rsquoinrsquo rsquorangersquo=gtarray(123))

array(rsquotagsrsquo rsquomatchrsquo rsquopatternrsquo=gtrsquo^[ws]+$rsquorsquomessagersquo=gtrsquoTags can only contain word charactersrsquo)

20 3 Post Management

array(rsquotagsrsquo rsquonormalizeTagsrsquo)

)

In the above we specify that the title content and status attributes are required thelength of title should not exceed 128 the status attribute value should be 1 (draft) 2(published) or 3 (archived) and the tags attribute should only contain word charactersand commas In addition we use normalizeTags to normalize the user-entered tags so thatthe tags are unique and properly separated with commas

The validators such as required length in and match are all built-in validators providedby Yii The normalizeTags validator is a method-based validator that we need to definein the Post class For more information about how to specify validation rules please referto the Guide

public function normalizeTags($attribute$params)

$this-gttags=Tagarray2string(array unique(Tagstring2array($this-gttags)))

The rules declared in this method are executed one by one when we call the validate() orsave() method of the model instance

Note It is very important to remember that attributes appearing in rules() mustbe those to be entered by end users Other attributes such as id and create time

in the Post model which are set by our code or database should not be in rules()For more details please refer to Securing Attribute Assignments

After making these changes we can visit the post creation page again to verify that thenew validation rules are taking effect

312 Customizing relations() Method

Lastly we customize the relations() method to specify the related objects of a post Bydeclaring these related objects in relations() we can exploit the powerful RelationalActiveRecord (RAR) feature to access the related object information of a post such asits author and comments without the need to write complex SQL JOIN statements

We customize the relations() method as follows

31 Customizing Post Model 21

public function relations()

return array(

rsquoauthorrsquo =gt array(selfBELONGS TO rsquoUserrsquo rsquoauthor idrsquo)

rsquocommentsrsquo =gt array(selfHAS MANY rsquoCommentrsquo rsquopost idrsquo

rsquoconditionrsquo=gtrsquocommentsstatus=rsquoCommentSTATUS APPROVED

rsquoorderrsquo=gtrsquocommentscreate time DESCrsquo)

rsquocommentCountrsquo =gt array(selfSTAT rsquoCommentrsquo rsquopost idrsquo

rsquoconditionrsquo=gtrsquostatus=rsquoCommentSTATUS APPROVED)

)

The above relations state that

bull A post belongs to an author whose class is User and the relationship is establishedbased on the author id attribute value of the post

bull A post has many comments whose class is Comment and the relationship is establishedbased on the post id attribute value of the comments These comments should besorted according to their creation time and the comments must be approved

bull The commentCount relation is a bit special as it returns back an aggregation resultwhich is about how many comments the post has

With the above relation declaration we can easily access the author and comments of apost like the following

$author=$post-gtauthor

echo $author-gtusername

$comments=$post-gtcomments

foreach($comments as $comment)

echo $comment-gtcontent

For more details about how to declare and use relations please refer to the Guide

313 Representing Status in Text

Because the status of a post is stored as an integer in the database we need to provide atextual representation so that it is more intuitive when being displayed to end users In alarge system the similar requirement is very common

22 3 Post Management

As a generic solution we use the tbl lookup table to store the mapping between integervalues and textual representations that are needed by other data objects We modify theLookup model class as follows to more easily access the textual data in the table

class Lookup extends CActiveRecord

private static $ items=array()

public static function items($type)

if(isset(self$ items[$type]))

selfloadItems($type)

return self$ items[$type]

public static function item($type$code)

if(isset(self$ items[$type]))

selfloadItems($type)

return isset(self$ items[$type][$code]) self$ items[$type][$code] false

private static function loadItems($type)

self$ items[$type]=array()

$models=selfmodel()-gtfindAll(array(

rsquoconditionrsquo=gtrsquotype=typersquo

rsquoparamsrsquo=gtarray(rsquotypersquo=gt$type)

rsquoorderrsquo=gtrsquopositionrsquo

))

foreach($models as $model)

self$ items[$type][$model-gtcode]=$model-gtname

Our new code mainly provides two static methods Lookupitems() and Lookupitem()The former returns a list of strings belonging to the specified data type while the latterreturns a particular string for the given data type and data value

Our blog database is pre-populated with two lookup types PostStatus and CommentStatusThe former refers to the possible post statuses while the latter the comment statuses

In order to make our code easier to read we also declare a set of constants to representthe status integer values We should use these constants through our code when referringto the corresponding status values

32 Creating and Updating Posts 23

class Post extends CActiveRecord

const STATUS DRAFT=0

const STATUS PUBLISHED=1

const STATUS ARCHIVED=2

Therefore we can call Lookupitems(rsquoPostStatusrsquo) to get the list of possible post statuses(text strings indexed by the corresponding integer values) and call Lookupitem(rsquoPostStatusrsquoPostSTATUS PUBLISHED) to get the string representation of the published status

32 Creating and Updating Posts

With the Post model ready we need to fine-tune the actions and views for the controllerPostController In this section we first customize the access control of CRUD operationswe then modify the code implementing the create and update operations

321 Customizing Access Control

The first thing we want to do is to customize the access control because the code generatedby yiic does not fit our needs

We modify the accessRules() method in the file wwwrootblogprotectedcontrollers

PostControllerphp as follows

public function accessRules()

return array(

array(rsquoallowrsquo allow all users to perform rsquolistrsquo and rsquoshowrsquo actions

rsquoactionsrsquo=gtarray(rsquoindexrsquo rsquoviewrsquo)

rsquousersrsquo=gtarray(rsquorsquo)

)

array(rsquoallowrsquo allow authenticated users to perform any action

rsquousersrsquo=gtarray(rsquorsquo)

)

array(rsquodenyrsquo deny all users

rsquousersrsquo=gtarray(rsquorsquo)

)

)

The above rules state that all users can access the index and view actions and authenti-cated users can access any actions including the admin action The user should be denied

24 3 Post Management

access in any other scenario Note that these rules are evaluated in the order they arelisted here The first rule matching the current context makes the access decision Forexample if the current user is the system owner who tries to visit the post creation pagethe second rule will match and it will give the access to the user

322 Customizing create and update Operations

The create and update operations are very similar They both need to display an HTMLform to collect user inputs validate them and save them into database The main differ-ence is that the update operation will pre-populate the form with the existing post datafound in the database For this reason the yiic tool generates a partial view wwwroot

blogprotectedviewspost formphp that is embedded in both the create and update

views to render the needed HTML form

We first change the formphp file so that the HTML form only collects the inputs wewant title content tags and status We use plain text fields to collect inputs for thefirst three attributes and a dropdown list to collect input for status The dropdown listoptions are the text displays of the possible post statuses

ltphp echo CHtmlactiveDropDownList($postrsquostatusrsquoLookupitems(rsquoPostStatusrsquo)) gt

In the above we call Lookupitems(rsquoPostStatusrsquo) to bring back the list of post statuses

We then modify the Post class so that it can automatically set some attributes (eg create

time author id) before a post is saved to the database We override the beforeSave()

method as follows

protected function beforeSave()

if(parentbeforeSave())

if($this-gtisNewRecord)

$this-gtcreate time=$this-gtupdate time=time()

$this-gtauthor id=Yiiapp()-gtuser-gtid

else

$this-gtupdate time=time()

return true

else

return false

33 Displaying Posts 25

When we save a post we want to update the tbl tag table to reflect the change of tagfrequencies We can do this work in the afterSave() method which is automaticallyinvoked by Yii after a post is successfully saved into the database

protected function afterSave()

parentafterSave()

Tagmodel()-gtupdateFrequency($this-gt oldTags $this-gttags)

private $ oldTags

protected function afterFind()

parentafterFind()

$this-gt oldTags=$this-gttags

In the implementation because we want to detect if the user changes the tags in case he isupdating an existing post we need to know what the old tags are For this reason we alsowrite the afterFind() method to keep the old tags in the variable oldTags The methodafterFind() is invoked automatically by Yii when an AR record is populated with thedata from database

We are not going to give details of the TagupdateFrequency() method here Interestedreaders may refer to the file wwwrootyiidemosblogprotectedmodelsTagphp

33 Displaying Posts

In our blog application a post may be displayed among a list of posts or by itself Theformer is implemented as the index operation while the latter the view operation In thissection we customize both operations to fulfill our initial requirements

331 Customizing view Operation

The view operation is implemented by the actionView() method in PostController Itsdisplay is generated by the view view with the view file wwwrootblogprotectedviews

postviewphp

Below is the relevant code implementing the view operation in PostController

public function actionView()

26 3 Post Management

$this-gtrender(rsquoviewrsquoarray(

$post=$this-gtloadModel()

$this-gtrender(rsquoviewrsquoarray(

rsquomodelrsquo=gt$post

))

))

private $ model

public function loadModel()

if($this-gt model===null)

if(isset($ GET[rsquoidrsquo]))

if(Yiiapp()-gtuser-gtisGuest)

$condition=rsquostatus=rsquoPostSTATUS PUBLISHED

rsquo OR status=rsquoPOSTSTATUS ARCHIVED

else

$condition=rsquorsquo

$this-gt model=Postmodel()-gtfindbyPk($ GET[rsquoidrsquo] $condition)

if($this-gt model===null)

throw new CHttpException(404rsquoThe requested page does not existrsquo)

return $this-gt model

Our change mainly lies in the loadModel() method In this method we query the Post

table according to the id GET parameter If the post is not found or if it is not publishedor archived (when the user is a guest) we will throw a 404 HTTP error Otherwise thepost object is returned to actionView() which in turn passes the post object to the viewscript for further display

Tip Yii captures HTTP exceptions (instances of CHttpException) and displaysthem in either predefined templates or customized error views The skeleton appli-cation generated by yiic already contains a customized error view in wwwroot

blogprotectedviewssiteerrorphp We can modify this file if we want tofurther customize the error display

The change in the view script is mainly about ajdusting the formatting and styles of thepost display We will not go into details here Interested readers may refer to wwwroot

blogprotectedviewspostviewphp

33 Displaying Posts 27

332 Customizing index Operation

Like the view operation we customize the index operation in two places the actionIndex()

method in PostController and the view file wwwrootblogprotectedviewspostindex

php We mainly need to add the support for displaying a list of posts that are associatedwith a specified tag

Below is the modified actionIndex() method in PostController

public function actionIndex()

$criteria=new CDbCriteria(array(

rsquoconditionrsquo=gtrsquostatus=rsquoPostSTATUS PUBLISHED

rsquoorderrsquo=gtrsquoupdate time DESCrsquo

rsquowithrsquo=gtrsquocommentCountrsquo

))

if(isset($ GET[rsquotagrsquo]))

$criteria-gtaddSearchCondition(rsquotagsrsquo$ GET[rsquotagrsquo])

$dataProvider=new CActiveDataProvider(rsquoPostrsquo array(

rsquopaginationrsquo=gtarray(

rsquopageSizersquo=gtYiiapp()-gtparams[rsquopostsPerPagersquo]

)

rsquocriteriarsquo=gt$criteria

))

$this-gtrender(rsquoindexrsquoarray(

rsquodataProviderrsquo=gt$dataProvider

))

In the above we first create a query criteria for retrieving post list The criteria statesthat only published posts should be returned and they should be sorted according to theirupdate time in descending order Because when displaying a post in the list we want toshow how many comments the post has received in the criteria we also specify to bringback commentCount which if you remember is a relation declared in Postrelations()

In case when a user wants to see posts with a specific tag we would add a search conditionto the criteria to look for the specified tag

Using the query criteria we create a data provider which mainly serves for three purposesFirst it does pagination of the data when too many results may be returned Herewe customize the pagination by setting the page size to be an application parameterpostsPerPage Second it does sorting according to the user request And finally it feedsthe paginated and sorted data to widgets or view code for presentation

28 3 Post Management

After we finish with actionIndex() we modify the index view as follows Our change ismainly about adding the h1 header when the user specifies to display posts with a tag

ltphp if(empty($ GET[rsquotagrsquo])) gt

lth1gtPosts Tagged with ltigtltphp echo CHtmlencode($ GET[rsquotagrsquo]) gtltigtlth1gt

ltphp endif gt

ltphp $this-gtwidget(rsquoziiwidgetsCListViewrsquo array(

rsquodataProviderrsquo=gt$dataProvider

rsquoitemViewrsquo=gtrsquo viewrsquo

rsquotemplatersquo=gtitemsnpager)) gt

Note that in the above we use [CListView] to display the post list This widget requires apartial view to display the detail of each individual post Here we specify the partial viewto be view which means the file wwwrootblogprotectedviewspost viewphp In thisview script we can acccess the post instance being displayed via a local variable named$data

34 Managing Posts

Managing posts mainly refers to listing posts in an administrative view that allows us tosee posts with all statuses updating them and deleting them They are accomplished bythe admin operation and the delete operation respectively The code generated by yiic

does not need much modification Below we mainly explain how these two operations areimplemented

341 Listing Posts in Tabular View

The admin operation shows posts with all statuses in a tabular view The view supportssorting and pagination The following is the actionAdmin() method in PostController

public function actionAdmin()

$dataProvider=new CActiveDataProvider(rsquoPostrsquo array(

rsquosortrsquo=gtarray(

rsquodefaultOrderrsquo=gtrsquostatus update time DESCrsquo

)

))

$this-gtrender(rsquoadminrsquoarray(

rsquodataProviderrsquo=gt$dataProvider

))

34 Managing Posts 29

The above code is very similar to that in actionIndex() The main difference is in thequery criteria Here we do not specify any particular query criteria since we want to seeall posts We do customize the sort property of the data provider so that by default theposts are shown in the order of their statuses and update time

Below is the code for the admin view

lth1gtManage Postslth1gt

ltphp $this-gtwidget(rsquoziiwidgetsgridCGridViewrsquo array(

rsquodataProviderrsquo=gt$dataProvider

rsquocolumnsrsquo=gtarray(

array(

rsquonamersquo=gtrsquotitlersquo

rsquotypersquo=gtrsquorawrsquo

rsquovaluersquo=gtrsquoCHtmllink(CHtmlencode($data-gttitle) $data-gturl)rsquo

)

array(

rsquonamersquo=gtrsquostatusrsquo

rsquovaluersquo=gtrsquoLookupitem(PostStatus$data-gtstatus)rsquo

)

rsquocreate timedatetimersquo

rsquoupdate timedatetimersquo

array(

rsquoclassrsquo=gtrsquoCButtonColumnrsquo

)

)

)) gt

We use [CGridView] to display the posts It allows us to sort by a column and paginatethrough the posts if there are too many to be displayed in a single page Our change ismainly about how to display each column For example for the title column we specifythat it should be displayed as a hyperlink that points to the detailed view of the post

Tip When displaying text we call CHtmlencode() to encode HTML entities init This prevents from cross-site scripting attack

342 Deleting Posts

In the admin data grid there is a delete button in each row Clicking on the button shoulddelete the corresponding post Internally this triggers the delete action implemented asfollows

30 3 Post Management

public function actionDelete()

if(Yiiapp()-gtrequest-gtisPostRequest)

we only allow deletion via POST request

$this-gtloadModel()-gtdelete()

if(isset($ POST[rsquoajaxrsquo]))

$this-gtredirect(array(rsquoindexrsquo))

else

throw new CHttpException(400rsquoInvalid request Please do not repeat this request againrsquo)

The above code is the one generated by the yiic tool without any change We wouldlike to explain a little bit more about the checking on $ POST[rsquoajaxrsquo] The [CGridView]widget has a very nice feature that its sorting pagination and deletion operations are alldone in AJAX mode by default That means the whole page does not get reloaded if anyof the above operations is performed However it is also possible that the widget runs innon-AJAX mode (by setting its ajaxUpdate property to be false or disabling JavaScript onthe client side) It is necessary for the delete action to differentiate these two scenarios ifthe delete request is made via AJAX we should not redirect the user browser otherwisewe should

Deleting a post should also cause the deletion of all comments for that post In additionwe should also update the tbl tag table regarding the tags for the deleted post Both ofthese tasks can be achieved by writing an afterDelete method in the Post model class asfollows

protected function afterDelete()

parentafterDelete()

Commentmodel()-gtdeleteAll(rsquopost id=rsquo$this-gtid)

Tagmodel()-gtupdateFrequency($this-gttags rsquorsquo)

The above code is very straightforward it first deletes all those comments whose post id

is the same as the ID of the deleted post it then updates the tbl tag table for the tags

of the deleted post

34 Managing Posts 31

Tip We have to explicitly delete all comments for the deleted post here becauseSQLite does not really support foreign key constraints In a DBMS that supportsthis constraint (such as MySQL PostgreSQL) the foreign key constraint can be setup such that the DBMS automatically deletes the related comments if the post isdeleted In that case we no longer this explicit deletion call in our code

32 3 Post Management

Chapter 4

Comment Management

41 Customizing Comment Model

For the Comment model we mainly need to customize the rules() and attributeLabels()

methods The attributeLabels() method returns a mapping between attribute namesand attribute labels We do not need to touch relations() since the code generated bythe yiic tool is good enough

411 Customizing rules() Method

We first customize the validation rules generated by the yiic tool The following rules areused for comments

public function rules()

return array(

array(rsquocontent author emailrsquo rsquorequiredrsquo)

array(rsquoauthor email urlrsquo rsquolengthrsquo rsquomaxrsquo=gt128)

array(rsquoemailrsquorsquoemailrsquo)

array(rsquourlrsquorsquourlrsquo)

)

In the above we specify that the author email and content attributes are required thelength of author email and url cannot exceed 128 the email attribute must be a validemail address and the url attribute must be a valid URL

412 Customizing attributeLabels() Method

We then customize the attributeLabels() method to declare the label display for eachmodel attribute This method returns an array consisting of name-label pairs When wecall CHtmlactiveLabel() to display an attribute label

34 4 Comment Management

public function attributeLabels()

return array(

rsquoidrsquo =gt rsquoIdrsquo

rsquocontentrsquo =gt rsquoCommentrsquo

rsquostatusrsquo =gt rsquoStatusrsquo

rsquocreate timersquo =gt rsquoCreate Timersquo

rsquoauthorrsquo =gt rsquoNamersquo

rsquoemailrsquo =gt rsquoEmailrsquo

rsquourlrsquo =gt rsquoWebsitersquo

rsquopost idrsquo =gt rsquoPostrsquo

)

Tip If the label for an attribute is not declared in attributeLabels() an algo-rithm will be used to generate an appropriate label For example a label VerifyCode will be generated for attributes verify code or verifyCode

413 Customizing Saving Process

Because we want to record the creation time of a comment we override the beforeSave()

method of Comment like we do for the Post model

protected function beforeSave()

if(parentbeforeSave())

if($this-gtisNewRecord)

$this-gtcreate time=time()

return true

else

return false

42 Creating and Displaying Comments

In this section we implement the comment display and creation features

421 Displaying Comments

Instead of displaying and creating comments on individual pages we use the post detailpage (generated by the view action of PostController) Below the post content display

42 Creating and Displaying Comments 35

we display first a list of comments belonging to that post and then a comment creationform

In order to display comments on the post detail page we modify the view script wwwrootblogprotectedviewspostviewphp as follows

ltdiv id=commentsgt

ltphp if($model-gtcommentCountgt=1) gt

lth3gt

ltphp echo $model-gtcommentCount rsquocomment(s)rsquo gt

lth3gt

ltphp $this-gtrenderPartial(rsquo commentsrsquoarray(

rsquopostrsquo=gt$model

rsquocommentsrsquo=gt$model-gtcomments

)) gt

ltphp endif gt

ltdivgt

In the above we call renderPartial() to render a partial view named comments to displaythe list of comments belonging to the current post Note that in the view we use theexpression $model-gtcomments to retrieve the comments for the post This is valid becausewe have declared a comments relation in the Post class Evaluating this expression wouldtrigger an implicit JOIN database query to bring back the proper comments This featureis known as lazy relational query

The partial view comments is not very interesting It mainly goes through every commentand displays the detail of it Interested readers may refer to wwwrootyiidemosblog

protectedpost commentsphp

422 Creating Comments

To handle comment creation we first modify the actionView() method of PostController

as follows

public function actionView()

$post=$this-gtloadModel()

$comment=$this-gtnewComment($post)

$this-gtrender(rsquoviewrsquoarray(

rsquomodelrsquo=gt$post

rsquocommentrsquo=gt$comment

))

36 4 Comment Management

protected function newComment($post)

$comment=new Comment

if(isset($ POST[rsquoCommentrsquo]))

$comment-gtattributes=$ POST[rsquoCommentrsquo]

if($post-gtaddComment($comment))

if($comment-gtstatus==CommentSTATUS PENDING)

Yiiapp()-gtuser-gtsetFlash(rsquocommentSubmittedrsquorsquoThank yoursquo)

$this-gtrefresh()

return $comment

In the above we call the newComment() method before we render view In the newComment()

method we generate a Comment instance and check if the comment form is submitted If sowe try to add the comment for the post by calling $post-gtaddComment($comment) If it goesthrough we refresh the post detail page In case the comment needs to be approved we willshow a flash message to indicate this decision A flash message is usually a confirmationmessage displayed to end users If the user clicks on the refresh button of his browser themessage will disappear

We also need to modify wwwrootblogprotectedviewspostviewphp furthermore

ltdiv id=commentsgt

lth3gtLeave a Commentlth3gt

ltphp if(Yiiapp()-gtuser-gthasFlash(rsquocommentSubmittedrsquo)) gt

ltdiv class=successgt

ltphp echo Yiiapp()-gtuser-gtgetFlash(rsquocommentSubmittedrsquo) gt

ltdivgt

ltphp else gt

ltphp $this-gtrenderPartial(rsquocomment formrsquoarray(

rsquomodelrsquo=gt$comment

)) gt

ltphp endif gt

ltdivgtlt-- comments --gt

43 Managing Comments 37

In the above code we display the flash message if it is available If not we displaythe comment input form by rendering the partial view wwwrootblogprotectedviews

comment formphp

43 Managing Comments

Comment management includes updating deleting and approving comments These op-erations are implemented as actions in the CommentController class

431 Updating and Deleting Comments

The code generated by yiic for updating and deleting comments remains largely un-changed

432 Approving Comments

When comments are newly created they are in pending approval status and need to beapproved in order to be visible to guest users Approving a comment is mainly aboutchanging the status column of the comment

We create an actionApprove() method in CommentController as follows

public function actionApprove()

if(Yiiapp()-gtrequest-gtisPostRequest)

$comment=$this-gtloadModel()

$comment-gtapprove()

$this-gtredirect(array(rsquoindexrsquo))

else

throw new CHttpException(400rsquoInvalid requestrsquo)

In the above when the approve action is invoked via a POST request we call the approve()

method defined in the Comment model to change the status We then redirect the userbrowser to the page displaying the post that this comment belongs to

We also modify the actionIndex() method of Comment to show all comments We wouldlike to see comments pending approved to show up first

public function actionIndex()

38 4 Comment Management

$dataProvider=new CActiveDataProvider(rsquoCommentrsquo array(

rsquocriteriarsquo=gtarray(

rsquowithrsquo=gtrsquopostrsquo

rsquoorderrsquo=gtrsquotstatus tcreate time DESCrsquo

)

))

$this-gtrender(rsquoindexrsquoarray(

rsquodataProviderrsquo=gt$dataProvider

))

Notice that in the above code because both tbl post and tbl comment have columns statusand create time we need to disambiguate the corresponding column reference by prefixingthem with table alias names As described in the guide the alias for the primary table ina relational query is always t Therefore we are prefixing t to the status and create time

columns in the above code

Like the post index view the index view for CommentController uses [CListView] to displaythe comment list which in turn uses the partial view wwwrootblogprotectedviews

comment viewphp to display the detail of each individual comment We will not go intodetails here Interested readers may refer to the corresponding file in the blog demowwwrootyiidemosblogprotectedviewscomment viewphp

Chapter 5

Portlets

51 Customizing Page Layout

Features like rdquothe most recent commentsrdquo rdquotag cloudrdquo are better to be implemented inportlets A portlet is a pluggable user interface component that renders a fragment ofHTML code In this section we describe how to set up the portlet architecture for ourblog application

Before we go into details about developing portlets we first need to adjust our page layoutso that the portlets can be placed in a side bar

A page layout determines how to share common sections (eg header footer) amongdifferent pages and how to embed contents that are specific for different pages In Yiia page layout is represented as a view under the protectedviewslayouts folder Whenwe call the render() method in a controller the rendering result will be automaticallyembedded into this layout view before it is displayed to end users The default layoutview is named as main which means the wwwrootblogprotectedviewslayoutsmain

php file in our blog application

We could directly modify the main layout to add a side bar However doing so wouldmake some irrelevant pages such as about contact pages to have a side bar which is notalways desirable For this reason we need different layouts for different pages

We thus create two different layouts column1 and column2 The former displays the pagein a single column while the latter displays two columns in the page one of which is theside bar We will use column2 layout for the post and comment pages and column1 forthe rest of the pages Because both column1 and column2 still share the same header andfooter sections we let them to share the main layout

Based on the above description we create the column1 layout as follows

ltphp $this-gtbeginContent(rsquolayoutsmainrsquo) gt

ltdiv class=containergt

40 5 Portlets

ltdiv id=contentgt

ltphp echo $content gt

ltdivgtlt-- content --gt

ltdivgt

ltphp $this-gtendContent() gt

and the column2 layout as follows

ltphp $this-gtbeginContent(rsquolayoutsmainrsquo) gt

ltdiv class=containergt

ltdiv class=span-18gt

ltdiv id=contentgt

ltphp echo $content gt

ltdivgtlt-- content --gt

ltdivgt

ltdiv class=span-6 lastgt

ltdiv id=sidebargt

portlets to be placed here

ltdivgtlt-- sidebar --gt

ltdivgt

ltdivgt

ltphp $this-gtendContent() gt

As we can see both column1 and column2 are enclosed in a pair of beginContent andendContent calls They essentially use the specified view layoutsmain to decorate theenclosed content In all these layout views the variable $content refers to the dynamiccontent to be embedded into the layout

Besides customizing the layout view file we also need to adjust the CSS file wwwroot

blogcssmaincss so that the overall appearance would look like what we see in the blogdemo We will not go into details here

Info The skeleton application generated by the yiic tool uses the BluePrint CSSframework It provides a nice font reset and a robust grid system For more detailson how to use this framework please refer to its documentation

After creating the new layouts we need to set the layout property of controllers to makeuse of them So far we have three controllers SiteController PostController andCommentController We set SiteController to use column1 as the default layout whilethe rest two of the controllers use column2 for example

class PostController extends Controller

52 Creating User Menu Portlet 41

public $layout=rsquocolumn2rsquo

Tip If some view of the controller does not use the same layout as declaredby the layout property it may customize it in the view by a simple assignment$this-gtlayout=rsquonewlayoutrsquo

52 Creating User Menu Portlet

Based on the requirements analysis we need three different portlets the rdquouser menurdquoportlet the rdquotag cloudrdquo portlet and the rdquorecent commentsrdquo portlet We will develop theseportlets by extending the [CPortlet] widget provided by Yii

In this section we will develop our first concrete portlet - the user menu portlet whichdisplays a list of menu items that are only available to authenticated users The menucontains four items

bull Approve Comments a hyperlink that leads to a list of comments pending approval

bull Create New Post a hyperlink that leads to the post creation page

bull Manage Posts a hyperlink that leads to the post management page

bull Logout a link button that would log out the current user

521 Creating UserMenu Class

We create the UserMenu class to represent the logic part of the user menu portlet Theclass is saved in the file wwwrootblogprotectedcomponentsUserMenuphp which has thefollowing content

Yiiimport(rsquoziiwidgetsCPortletrsquo)

class UserMenu extends CPortlet

public function init()

$this-gttitle=CHtmlencode(Yiiapp()-gtuser-gtname)

parentinit()

42 5 Portlets

protected function renderContent()

$this-gtrender(rsquouserMenursquo)

The UserMenu class extends from the CPortlet class that we created previously It overridesboth the init() method and the renderContent() method of CPortlet The former setsthe portlet title to be the name of the current user the latter generates the portlet bodycontent by rendering a view named userMenu

Tip Notice that we have to explicitly include the CPortlet class by calling Yii

import() before we refer to it the first time This is because CPortlet is part of thezii project ndash the official extension library for Yii For performance considerationclasses in this project are not listed as core classes Therefore we have to import itbefore we use it the first time

522 Creating userMenu View

Next we create the userMenu view which is saved in the file wwwrootblogprotected

componentsviewsuserMenuphp

ltulgt

ltligtltphp echo CHtmllink(rsquoCreate New Postrsquoarray(rsquopostcreatersquo)) gtltligt

ltligtltphp echo CHtmllink(rsquoManage Postsrsquoarray(rsquopostadminrsquo)) gtltligt

ltligtltphp echo CHtmllink(rsquoApprove Commentsrsquoarray(rsquocommentindexrsquo))

rsquo (rsquo Commentmodel()-gtpendingCommentCount rsquo)rsquo gtltligt

ltligtltphp echo CHtmllink(rsquoLogoutrsquoarray(rsquositelogoutrsquo)) gtltligt

ltulgt

Info By default view files for a widget should be placed under the views sub-directory of the directory containing the widget class file The file name must bethe same as the view name

523 Using UserMenu Portlet

It is time for us to make use of our newly completed UserMenu portlet We modify thelayout view file wwwrootblogprotectedviewslayoutscolumn2php as follows

53 Creating Tag Cloud Portlet 43

ltdiv id=sidebargt

ltphp if(Yiiapp()-gtuser-gtisGuest) $this-gtwidget(rsquoUserMenursquo) gt

ltdivgt

In the above we call the widget() method to generate and execute an instance of theUserMenu class Because the portlet should only be displayed to authenticated users weonly call widget() when the isGuest property of the current user is false (meaning theuser is authenticated)

524 Testing UserMenu Portlet

Letrsquos test what we have so far

1 Open a browser window and enter the URL httpwwwexamplecomblogindex

php Verify that there is nothing displayed in the side bar section of the page

2 Click on the Login hyperlink and fill out the login form to login If successful verifythat the UserMenu portlet appears in the side bar and the portlet has the usernameas its title

3 Click on the rsquoLogoutrsquo hyperlink in the UserMenu portlet Verify that the logout actionis successful and the UserMenu portlet disappears

525 Summary

What we have created is a portlet that is highly reusable We can easily reuse it in adifferent project with little or no modification Moreover the design of this portlet followsclosely the philosophy that logic and presentation should be separated While we did notpoint this out in the previous sections such practice is used nearly everywhere in a typicalYii application

53 Creating Tag Cloud Portlet

Tag cloud displays a list of post tags with visual decorations hinting the popularity of eachindividual tag

531 Creating TagCloud Class

We create the TagCloud class in the file wwwrootblogprotectedcomponentsTagCloud

php The file has the following content

44 5 Portlets

Yiiimport(rsquoziiwidgetsCPortletrsquo)

class TagCloud extends CPortlet

public $title=rsquoTagsrsquo

public $maxTags=20

protected function renderContent()

$tags=Tagmodel()-gtfindTagWeights($this-gtmaxTags)

foreach($tags as $tag=gt$weight)

$link=CHtmllink(CHtmlencode($tag) array(rsquopostindexrsquorsquotagrsquo=gt$tag))

echo CHtmltag(rsquospanrsquo array(

rsquoclassrsquo=gtrsquotagrsquo

rsquostylersquo=gtfont-size$weightpt) $link)n

Unlike the UserMenu portlet the TagCloud portlet does not use a view Instead its presen-tation is done in the renderContent() method This is because the presentation does notcontain much HTML tags

We display each tag as a hyperlink to the post index page with the corresponding tagparameter The font size of each tag link is adjusted according to their relative weightamong other tags If a tag has higher frequency value than the other it will have a biggerfont size

532 Using TagCloud Portlet

Usage of the TagCloud portlet is very simple We modify the layout file wwwrootblog

protectedviewslayoutsmainphp as follows

ltdiv id=sidebargt

ltphp if(Yiiapp()-gtuser-gtisGuest) $this-gtwidget(rsquoUserMenursquo) gt

ltphp $this-gtwidget(rsquoTagCloudrsquo array(

rsquomaxTagsrsquo=gtYiiapp()-gtparams[rsquotagCloudCountrsquo]

)) gt

ltdivgt

54 Creating Recent Comments Portlet 45

54 Creating Recent Comments Portlet

In this section we create the last portlet that displays a list of comments recently pub-lished

541 Creating RecentComments Class

We create the RecentComments class in the file wwwrootblogprotectedcomponentsRecentCommentsphp The file has the following content

Yiiimport(rsquoziiwidgetsCPortletrsquo)

class RecentComments extends CPortlet

public $title=rsquoRecent Commentsrsquo

public $maxComments=10

public function getRecentComments()

return Commentmodel()-gtfindRecentComments($this-gtmaxComments)

protected function renderContent()

$this-gtrender(rsquorecentCommentsrsquo)

In the above we invoke the findRecentComments method which is defined in the Comment

class as follows

class Comment extends CActiveRecord

public function findRecentComments($limit=10)

return $this-gtwith(rsquopostrsquo)-gtfindAll(array(

rsquoconditionrsquo=gtrsquotstatus=rsquoselfSTATUS APPROVED

rsquoorderrsquo=gtrsquotcreate time DESCrsquo

rsquolimitrsquo=gt$limit

))

46 5 Portlets

542 Creating recentComments View

The recentComments view is saved in the file wwwrootblogprotectedcomponentsviews

recentCommentsphp It simply displays every comment returned by the RecentComments

getRecentComments() method

543 Using RecentComments Portlet

We modify the layout file wwwrootblogprotectedviewslayoutsmainphp to embedthis last portlet

ltdiv id=sidebargt

ltphp if(Yiiapp()-gtuser-gtisGuest) $this-gtwidget(rsquoUserMenursquo) gt

ltphp $this-gtwidget(rsquoTagCloudrsquo array(

rsquomaxTagsrsquo=gtYiiapp()-gtparams[rsquotagCloudCountrsquo]

)) gt

ltphp $this-gtwidget(rsquoRecentCommentsrsquo array(

rsquomaxCommentsrsquo=gtYiiapp()-gtparams[rsquorecentCommentCountrsquo]

)) gt

ltdivgt

Chapter 6

Final Work

61 Beautifying URLs

The URLs linking various pages of our blog application currently look ugly For examplethe URL for the page showing a post looks like the following

indexphpr=postshowampid=1

In this section we describe how to beautify these URLs and make them SEO-friendlyOur goal is to be able to use the following URLs in the application

1 indexphppostsyii leads to the page showing a list of posts with tag yii

2 indexphppost2A+Test+Post leads to the page showing the detail of the postwith ID 2 whose title is A Test Post

3 indexphppostupdate1 leads to the page that allows updating the post with ID1

Note that in the second URL format we include the post title in the URL This is mainlyto make the URL SEO friendly It is said that search engines may also respect the wordsfound in a URL when it is being indexed

To achieve our goal we modify the application configuration as follows

return array(

rsquocomponentsrsquo=gtarray(

rsquourlManagerrsquo=gtarray(

rsquourlFormatrsquo=gtrsquopathrsquo

rsquorulesrsquo=gtarray(

48 6 Final Work

rsquopostltidd+gtlttitlegtrsquo=gtrsquopostviewrsquorsquopostslttaggtrsquo=gtrsquopostindexrsquo

)

)

)

)

In the above we configure the urlManager component by setting its urlFormat propertyto be path and adding a set of rules

The rules are used by urlManager to parse and create the URLs in the desired formatFor example the first rule says that if a URL indexphppostsyii is requested theurlManager component should be responsible to dispatch the request to the route post

index and generate a tag GET parameter with the value yii On the other hand whencreating a URL with the route postindex and parameter tag the urlManager componentwill also use this rule to generate the desired URL indexphppostsyii For this reasonwe say that urlManager is a two-way URL manager

The urlManager component can further beautify our URLs such as hiding indexphp inthe URLs appending suffix like html to the URLs We can obtain these features easilyby configuring various properties of urlManager in the application configuration For moredetails please refer to the Guide

62 Logging Errors

A production Web application often needs sophisticated logging for various events In ourblog application we would like to log the errors occurring when it is being used Sucherrors could be programming mistakes or usersrsquo misuse of the system Logging these errorswill help us to improve the blog application

We enable the error logging by modifying the application configuration as follows

return array(

rsquopreloadrsquo=gtarray(rsquologrsquo)

rsquocomponentsrsquo=gtarray(

rsquologrsquo=gtarray(

rsquoclassrsquo=gtrsquoCLogRouterrsquo

rsquoroutesrsquo=gtarray(

array(

rsquoclassrsquo=gtrsquoCFileLogRoutersquo

rsquolevelsrsquo=gtrsquoerror warningrsquo

63 Final Tune-up and Deployment 49

)

)

)

)

)

With the above configuration if an error or warning occurs detailed information will belogged and saved in a file located under the directory wwwrootblogprotectedruntime

The log component offers more advanced features such as sending log messages to a listof email addresses displaying log messages in JavaScript console window etc For moredetails please refer to the Guide

63 Final Tune-up and Deployment

We are close to finish our blog application Before deployment we would like to do sometune-ups

631 Changing Home Page

We change to use the post list page as the home page We modify the application config-uration as follows

return array(

rsquodefaultControllerrsquo=gtrsquopostrsquo

)

Tip Because PostController already declares list to be its default action whenwe access the home page of the application we will see the result generated by thelist action of the post controller

632 Enabling Schema Caching

Because ActiveRecord relies on the metadata about tables to determine the column infor-mation it takes time to read the metadata and analyze it This may not be a problemduring development stage but for an application running in production mode it is a totalwaste of time if the database schema does not change Therefore we should enable theschema caching by modifying the application configuration as follows

50 6 Final Work

return array(

rsquocomponentsrsquo=gtarray(

rsquocachersquo=gtarray(

rsquoclassrsquo=gtrsquoCDbCachersquo

)

rsquodbrsquo=gtarray(

rsquoclassrsquo=gtrsquosystemdbCDbConnectionrsquo

rsquoconnectionStringrsquo=gtrsquosqlitewwwrootblogprotecteddatablogdbrsquo

rsquoschemaCachingDurationrsquo=gt3600

)

)

)

In the above we first add a cache component which uses a default SQLite database as thecaching storage If our server is equipped with other caching extensions such as APCwe could change to use them as well We also modify the db component by setting itsschemaCachingDuration property to be 3600 which means the parsed database schemadata can remain valid in cache for 3600 seconds

633 Disabling Debugging Mode

We modify the entry script file wwwrootblogindexphp by removing the line definingthe constant YII DEBUG This constant is useful during development stage because it allowsYii to display more debugging information when an error occurs However when theapplication is running in production mode displaying debugging information is not agood idea because it may contain sensitive information such as where the script file islocated and the content in the file etc

634 Deploying the Application

The final deployment process manly involves copying the directory wwwrootblog to thetarget directory The following checklist shows every needed step

1 Install Yii in the target place if it is not available

2 Copy the entire directory wwwrootblog to the target place

3 Edit the entry script file indexphp by pointing the $yii variable to the new Yiibootstrap file

4 Edit the file protectedyiicphp by setting the $yiic variable to be the new Yiiyiicphp file

64 Future Enhancements 51

5 Change the permission of the directories assets and protectedruntime so that theyare writable by the Web server process

64 Future Enhancements

641 Using a Theme

Without writing any code our blog application is already themeable To use a themewe mainly need to develop the theme by writing customized view files in the theme Forexample to use a theme named classic that uses a different page layout we would createa layout view file wwwrootblogthemesclassicviewslayoutsmainphp We also needto change the application configuration to indicate our choice of the classic theme

return array(

rsquothemersquo=gtrsquoclassicrsquo

)

642 Internationalization

We may also internationalize our blog application so that its pages can be displayed indifferent languages This mainly involves efforts in two aspects

First we may create view files in different languages For example for the list pageof PostController we can create a view file wwwrootblogprotectedviewspostzh cn

listphp When the application is configured to use simplified Chinese (the language codeis zh cn) Yii will automatically use this new view file instead of the original one

Second we may create message translations for those messages generated by code Themessage translations should be saved as files under the directory wwwrootblogprotected

messages We also need to modify the code where we use text strings by enclosing themin the method call Yiit()

For more details about internationalization please refer to the Guide

643 Improving Performance with Cache

While the Yii framework itself is very efficient it is not necessarily true that an applicationwritten in Yii efficient There are several places in our blog application that we can improvethe performance For example the tag clould portlet could be one of the performancebottlenecks because it involves complex database query and PHP logic

52 6 Final Work

We can make use of the sophisticated caching feature provided by Yii to improve theperformance One of the most useful components in Yii is COutputCache which caches afragment of page display so that the underlying code generating the fragment does not needto be executed for every request For example in the layout file wwwrootblogprotected

viewslayoutsmainphp we can enclose the tag cloud portlet with COutputCache

ltphp if($this-gtbeginCache(rsquotagCloudrsquo array(rsquodurationrsquo=gt3600))) gt

ltphp $this-gtwidget(rsquoTagCloudrsquo array(

rsquomaxTagsrsquo=gtYiiapp()-gtparams[rsquotagCloudCountrsquo]

)) gt

ltphp $this-gtendCache() gt

With the above code the tag cloud display will be served from cache instead of beinggenerated on-the-fly for every request The cached content will remain valid in cache for3600 seconds

644 Adding New Features

Our blog application only has very basic functionalities To become a complete blogsystem more features are needed for example calendar portlet email notifications postcategorization archived post portlet and so on We will leave the implementation of thesefeatures to interested readers

  • Contents
  • License
  • Getting Started
    • Building a Blog System using Yii
    • Testdriving with Yii
      • Installing Yii
      • Creating Skeleton Application
      • Application Workflow
        • Requirements Analysis
        • Overall Design
          • Initial Prototyping
            • Setting Up Database
              • Creating Database
              • Establishing Database Connection
                • Scaffolding
                • Authenticating User
                • Summary
                  • Post Management
                    • Customizing Post Model
                      • Customizing rules() Method
                      • Customizing relations() Method
                      • Representing Status in Text
                        • Creating and Updating Posts
                          • Customizing Access Control
                          • Customizing create and update Operations
                            • Displaying Posts
                              • Customizing view Operation
                              • Customizing index Operation
                                • Managing Posts
                                  • Listing Posts in Tabular View
                                  • Deleting Posts
                                      • Comment Management
                                        • Customizing Comment Model
                                          • Customizing rules() Method
                                          • Customizing attributeLabels() Method
                                          • Customizing Saving Process
                                            • Creating and Displaying Comments
                                              • Displaying Comments
                                              • Creating Comments
                                                • Managing Comments
                                                  • Updating and Deleting Comments
                                                  • Approving Comments
                                                      • Portlets
                                                        • Customizing Page Layout
                                                        • Creating User Menu Portlet
                                                          • Creating UserMenu Class
                                                          • Creating userMenu View
                                                          • Using UserMenu Portlet
                                                          • Testing UserMenu Portlet
                                                          • Summary
                                                            • Creating Tag Cloud Portlet
                                                              • Creating TagCloud Class
                                                              • Using TagCloud Portlet
                                                                • Creating Recent Comments Portlet
                                                                  • Creating RecentComments Class
                                                                  • Creating recentComments View
                                                                  • Using RecentComments Portlet
                                                                      • Final Work
                                                                        • Beautifying URLs
                                                                        • Logging Errors
                                                                        • Final Tune-up and Deployment
                                                                          • Changing Home Page
                                                                          • Enabling Schema Caching
                                                                          • Disabling Debugging Mode
                                                                          • Deploying the Application
                                                                            • Future Enhancements
                                                                              • Using a Theme
                                                                              • Internationalization
                                                                              • Improving Performance with Cache
                                                                              • Adding New Features
Page 25: Yii Blog 1.1.0 Demo Tutor

18 2 Initial Prototyping

For a new project most of the time will be spent in step 1 and 4 for this first milestone

Although the code generated by the yiic tool implements fully functional CRUD oper-ations for a database table it often needs to be modified in practical applications Forthis reason in the next two milestone our job is to customize the generated CRUD codeabout posts and comments so that it reaches our initial requirements

In general we first modify the model class file by adding appropriate validation rules anddeclaring relational objects We then modify the controller action and view code for eachindividual CRUD operation

Chapter 3

Post Management

31 Customizing Post Model

The Post model class generated by the yiic tool mainly needs to be modified in two places

bull the rules() method specifies the validation rules for the model attributes

bull the relations() method specifies the related objects

Info A model consists of a list of attributes each associated with a column in thecorresponding database table Attributes can be declared explicitly as class membervariables or implicitly without any declaration

311 Customizing rules() Method

We first specify the validation rules which ensure the attribute values entered by usersare correct before they are saved to the database For example the status attribute ofPost should be an integer 1 2 or 3 The yiic tool also generates validation rules for eachmodel However these rules are based on the table column information and may not beappropriate

Based on the requirement analysis we modify the rules() method as follows

public function rules()

return array(

array(rsquotitle content statusrsquo rsquorequiredrsquo)

array(rsquotitlersquo rsquolengthrsquo rsquomaxrsquo=gt128)

array(rsquostatusrsquo rsquoinrsquo rsquorangersquo=gtarray(123))

array(rsquotagsrsquo rsquomatchrsquo rsquopatternrsquo=gtrsquo^[ws]+$rsquorsquomessagersquo=gtrsquoTags can only contain word charactersrsquo)

20 3 Post Management

array(rsquotagsrsquo rsquonormalizeTagsrsquo)

)

In the above we specify that the title content and status attributes are required thelength of title should not exceed 128 the status attribute value should be 1 (draft) 2(published) or 3 (archived) and the tags attribute should only contain word charactersand commas In addition we use normalizeTags to normalize the user-entered tags so thatthe tags are unique and properly separated with commas

The validators such as required length in and match are all built-in validators providedby Yii The normalizeTags validator is a method-based validator that we need to definein the Post class For more information about how to specify validation rules please referto the Guide

public function normalizeTags($attribute$params)

$this-gttags=Tagarray2string(array unique(Tagstring2array($this-gttags)))

The rules declared in this method are executed one by one when we call the validate() orsave() method of the model instance

Note It is very important to remember that attributes appearing in rules() mustbe those to be entered by end users Other attributes such as id and create time

in the Post model which are set by our code or database should not be in rules()For more details please refer to Securing Attribute Assignments

After making these changes we can visit the post creation page again to verify that thenew validation rules are taking effect

312 Customizing relations() Method

Lastly we customize the relations() method to specify the related objects of a post Bydeclaring these related objects in relations() we can exploit the powerful RelationalActiveRecord (RAR) feature to access the related object information of a post such asits author and comments without the need to write complex SQL JOIN statements

We customize the relations() method as follows

31 Customizing Post Model 21

public function relations()

return array(

rsquoauthorrsquo =gt array(selfBELONGS TO rsquoUserrsquo rsquoauthor idrsquo)

rsquocommentsrsquo =gt array(selfHAS MANY rsquoCommentrsquo rsquopost idrsquo

rsquoconditionrsquo=gtrsquocommentsstatus=rsquoCommentSTATUS APPROVED

rsquoorderrsquo=gtrsquocommentscreate time DESCrsquo)

rsquocommentCountrsquo =gt array(selfSTAT rsquoCommentrsquo rsquopost idrsquo

rsquoconditionrsquo=gtrsquostatus=rsquoCommentSTATUS APPROVED)

)

The above relations state that

bull A post belongs to an author whose class is User and the relationship is establishedbased on the author id attribute value of the post

bull A post has many comments whose class is Comment and the relationship is establishedbased on the post id attribute value of the comments These comments should besorted according to their creation time and the comments must be approved

bull The commentCount relation is a bit special as it returns back an aggregation resultwhich is about how many comments the post has

With the above relation declaration we can easily access the author and comments of apost like the following

$author=$post-gtauthor

echo $author-gtusername

$comments=$post-gtcomments

foreach($comments as $comment)

echo $comment-gtcontent

For more details about how to declare and use relations please refer to the Guide

313 Representing Status in Text

Because the status of a post is stored as an integer in the database we need to provide atextual representation so that it is more intuitive when being displayed to end users In alarge system the similar requirement is very common

22 3 Post Management

As a generic solution we use the tbl lookup table to store the mapping between integervalues and textual representations that are needed by other data objects We modify theLookup model class as follows to more easily access the textual data in the table

class Lookup extends CActiveRecord

private static $ items=array()

public static function items($type)

if(isset(self$ items[$type]))

selfloadItems($type)

return self$ items[$type]

public static function item($type$code)

if(isset(self$ items[$type]))

selfloadItems($type)

return isset(self$ items[$type][$code]) self$ items[$type][$code] false

private static function loadItems($type)

self$ items[$type]=array()

$models=selfmodel()-gtfindAll(array(

rsquoconditionrsquo=gtrsquotype=typersquo

rsquoparamsrsquo=gtarray(rsquotypersquo=gt$type)

rsquoorderrsquo=gtrsquopositionrsquo

))

foreach($models as $model)

self$ items[$type][$model-gtcode]=$model-gtname

Our new code mainly provides two static methods Lookupitems() and Lookupitem()The former returns a list of strings belonging to the specified data type while the latterreturns a particular string for the given data type and data value

Our blog database is pre-populated with two lookup types PostStatus and CommentStatusThe former refers to the possible post statuses while the latter the comment statuses

In order to make our code easier to read we also declare a set of constants to representthe status integer values We should use these constants through our code when referringto the corresponding status values

32 Creating and Updating Posts 23

class Post extends CActiveRecord

const STATUS DRAFT=0

const STATUS PUBLISHED=1

const STATUS ARCHIVED=2

Therefore we can call Lookupitems(rsquoPostStatusrsquo) to get the list of possible post statuses(text strings indexed by the corresponding integer values) and call Lookupitem(rsquoPostStatusrsquoPostSTATUS PUBLISHED) to get the string representation of the published status

32 Creating and Updating Posts

With the Post model ready we need to fine-tune the actions and views for the controllerPostController In this section we first customize the access control of CRUD operationswe then modify the code implementing the create and update operations

321 Customizing Access Control

The first thing we want to do is to customize the access control because the code generatedby yiic does not fit our needs

We modify the accessRules() method in the file wwwrootblogprotectedcontrollers

PostControllerphp as follows

public function accessRules()

return array(

array(rsquoallowrsquo allow all users to perform rsquolistrsquo and rsquoshowrsquo actions

rsquoactionsrsquo=gtarray(rsquoindexrsquo rsquoviewrsquo)

rsquousersrsquo=gtarray(rsquorsquo)

)

array(rsquoallowrsquo allow authenticated users to perform any action

rsquousersrsquo=gtarray(rsquorsquo)

)

array(rsquodenyrsquo deny all users

rsquousersrsquo=gtarray(rsquorsquo)

)

)

The above rules state that all users can access the index and view actions and authenti-cated users can access any actions including the admin action The user should be denied

24 3 Post Management

access in any other scenario Note that these rules are evaluated in the order they arelisted here The first rule matching the current context makes the access decision Forexample if the current user is the system owner who tries to visit the post creation pagethe second rule will match and it will give the access to the user

322 Customizing create and update Operations

The create and update operations are very similar They both need to display an HTMLform to collect user inputs validate them and save them into database The main differ-ence is that the update operation will pre-populate the form with the existing post datafound in the database For this reason the yiic tool generates a partial view wwwroot

blogprotectedviewspost formphp that is embedded in both the create and update

views to render the needed HTML form

We first change the formphp file so that the HTML form only collects the inputs wewant title content tags and status We use plain text fields to collect inputs for thefirst three attributes and a dropdown list to collect input for status The dropdown listoptions are the text displays of the possible post statuses

ltphp echo CHtmlactiveDropDownList($postrsquostatusrsquoLookupitems(rsquoPostStatusrsquo)) gt

In the above we call Lookupitems(rsquoPostStatusrsquo) to bring back the list of post statuses

We then modify the Post class so that it can automatically set some attributes (eg create

time author id) before a post is saved to the database We override the beforeSave()

method as follows

protected function beforeSave()

if(parentbeforeSave())

if($this-gtisNewRecord)

$this-gtcreate time=$this-gtupdate time=time()

$this-gtauthor id=Yiiapp()-gtuser-gtid

else

$this-gtupdate time=time()

return true

else

return false

33 Displaying Posts 25

When we save a post we want to update the tbl tag table to reflect the change of tagfrequencies We can do this work in the afterSave() method which is automaticallyinvoked by Yii after a post is successfully saved into the database

protected function afterSave()

parentafterSave()

Tagmodel()-gtupdateFrequency($this-gt oldTags $this-gttags)

private $ oldTags

protected function afterFind()

parentafterFind()

$this-gt oldTags=$this-gttags

In the implementation because we want to detect if the user changes the tags in case he isupdating an existing post we need to know what the old tags are For this reason we alsowrite the afterFind() method to keep the old tags in the variable oldTags The methodafterFind() is invoked automatically by Yii when an AR record is populated with thedata from database

We are not going to give details of the TagupdateFrequency() method here Interestedreaders may refer to the file wwwrootyiidemosblogprotectedmodelsTagphp

33 Displaying Posts

In our blog application a post may be displayed among a list of posts or by itself Theformer is implemented as the index operation while the latter the view operation In thissection we customize both operations to fulfill our initial requirements

331 Customizing view Operation

The view operation is implemented by the actionView() method in PostController Itsdisplay is generated by the view view with the view file wwwrootblogprotectedviews

postviewphp

Below is the relevant code implementing the view operation in PostController

public function actionView()

26 3 Post Management

$this-gtrender(rsquoviewrsquoarray(

$post=$this-gtloadModel()

$this-gtrender(rsquoviewrsquoarray(

rsquomodelrsquo=gt$post

))

))

private $ model

public function loadModel()

if($this-gt model===null)

if(isset($ GET[rsquoidrsquo]))

if(Yiiapp()-gtuser-gtisGuest)

$condition=rsquostatus=rsquoPostSTATUS PUBLISHED

rsquo OR status=rsquoPOSTSTATUS ARCHIVED

else

$condition=rsquorsquo

$this-gt model=Postmodel()-gtfindbyPk($ GET[rsquoidrsquo] $condition)

if($this-gt model===null)

throw new CHttpException(404rsquoThe requested page does not existrsquo)

return $this-gt model

Our change mainly lies in the loadModel() method In this method we query the Post

table according to the id GET parameter If the post is not found or if it is not publishedor archived (when the user is a guest) we will throw a 404 HTTP error Otherwise thepost object is returned to actionView() which in turn passes the post object to the viewscript for further display

Tip Yii captures HTTP exceptions (instances of CHttpException) and displaysthem in either predefined templates or customized error views The skeleton appli-cation generated by yiic already contains a customized error view in wwwroot

blogprotectedviewssiteerrorphp We can modify this file if we want tofurther customize the error display

The change in the view script is mainly about ajdusting the formatting and styles of thepost display We will not go into details here Interested readers may refer to wwwroot

blogprotectedviewspostviewphp

33 Displaying Posts 27

332 Customizing index Operation

Like the view operation we customize the index operation in two places the actionIndex()

method in PostController and the view file wwwrootblogprotectedviewspostindex

php We mainly need to add the support for displaying a list of posts that are associatedwith a specified tag

Below is the modified actionIndex() method in PostController

public function actionIndex()

$criteria=new CDbCriteria(array(

rsquoconditionrsquo=gtrsquostatus=rsquoPostSTATUS PUBLISHED

rsquoorderrsquo=gtrsquoupdate time DESCrsquo

rsquowithrsquo=gtrsquocommentCountrsquo

))

if(isset($ GET[rsquotagrsquo]))

$criteria-gtaddSearchCondition(rsquotagsrsquo$ GET[rsquotagrsquo])

$dataProvider=new CActiveDataProvider(rsquoPostrsquo array(

rsquopaginationrsquo=gtarray(

rsquopageSizersquo=gtYiiapp()-gtparams[rsquopostsPerPagersquo]

)

rsquocriteriarsquo=gt$criteria

))

$this-gtrender(rsquoindexrsquoarray(

rsquodataProviderrsquo=gt$dataProvider

))

In the above we first create a query criteria for retrieving post list The criteria statesthat only published posts should be returned and they should be sorted according to theirupdate time in descending order Because when displaying a post in the list we want toshow how many comments the post has received in the criteria we also specify to bringback commentCount which if you remember is a relation declared in Postrelations()

In case when a user wants to see posts with a specific tag we would add a search conditionto the criteria to look for the specified tag

Using the query criteria we create a data provider which mainly serves for three purposesFirst it does pagination of the data when too many results may be returned Herewe customize the pagination by setting the page size to be an application parameterpostsPerPage Second it does sorting according to the user request And finally it feedsthe paginated and sorted data to widgets or view code for presentation

28 3 Post Management

After we finish with actionIndex() we modify the index view as follows Our change ismainly about adding the h1 header when the user specifies to display posts with a tag

ltphp if(empty($ GET[rsquotagrsquo])) gt

lth1gtPosts Tagged with ltigtltphp echo CHtmlencode($ GET[rsquotagrsquo]) gtltigtlth1gt

ltphp endif gt

ltphp $this-gtwidget(rsquoziiwidgetsCListViewrsquo array(

rsquodataProviderrsquo=gt$dataProvider

rsquoitemViewrsquo=gtrsquo viewrsquo

rsquotemplatersquo=gtitemsnpager)) gt

Note that in the above we use [CListView] to display the post list This widget requires apartial view to display the detail of each individual post Here we specify the partial viewto be view which means the file wwwrootblogprotectedviewspost viewphp In thisview script we can acccess the post instance being displayed via a local variable named$data

34 Managing Posts

Managing posts mainly refers to listing posts in an administrative view that allows us tosee posts with all statuses updating them and deleting them They are accomplished bythe admin operation and the delete operation respectively The code generated by yiic

does not need much modification Below we mainly explain how these two operations areimplemented

341 Listing Posts in Tabular View

The admin operation shows posts with all statuses in a tabular view The view supportssorting and pagination The following is the actionAdmin() method in PostController

public function actionAdmin()

$dataProvider=new CActiveDataProvider(rsquoPostrsquo array(

rsquosortrsquo=gtarray(

rsquodefaultOrderrsquo=gtrsquostatus update time DESCrsquo

)

))

$this-gtrender(rsquoadminrsquoarray(

rsquodataProviderrsquo=gt$dataProvider

))

34 Managing Posts 29

The above code is very similar to that in actionIndex() The main difference is in thequery criteria Here we do not specify any particular query criteria since we want to seeall posts We do customize the sort property of the data provider so that by default theposts are shown in the order of their statuses and update time

Below is the code for the admin view

lth1gtManage Postslth1gt

ltphp $this-gtwidget(rsquoziiwidgetsgridCGridViewrsquo array(

rsquodataProviderrsquo=gt$dataProvider

rsquocolumnsrsquo=gtarray(

array(

rsquonamersquo=gtrsquotitlersquo

rsquotypersquo=gtrsquorawrsquo

rsquovaluersquo=gtrsquoCHtmllink(CHtmlencode($data-gttitle) $data-gturl)rsquo

)

array(

rsquonamersquo=gtrsquostatusrsquo

rsquovaluersquo=gtrsquoLookupitem(PostStatus$data-gtstatus)rsquo

)

rsquocreate timedatetimersquo

rsquoupdate timedatetimersquo

array(

rsquoclassrsquo=gtrsquoCButtonColumnrsquo

)

)

)) gt

We use [CGridView] to display the posts It allows us to sort by a column and paginatethrough the posts if there are too many to be displayed in a single page Our change ismainly about how to display each column For example for the title column we specifythat it should be displayed as a hyperlink that points to the detailed view of the post

Tip When displaying text we call CHtmlencode() to encode HTML entities init This prevents from cross-site scripting attack

342 Deleting Posts

In the admin data grid there is a delete button in each row Clicking on the button shoulddelete the corresponding post Internally this triggers the delete action implemented asfollows

30 3 Post Management

public function actionDelete()

if(Yiiapp()-gtrequest-gtisPostRequest)

we only allow deletion via POST request

$this-gtloadModel()-gtdelete()

if(isset($ POST[rsquoajaxrsquo]))

$this-gtredirect(array(rsquoindexrsquo))

else

throw new CHttpException(400rsquoInvalid request Please do not repeat this request againrsquo)

The above code is the one generated by the yiic tool without any change We wouldlike to explain a little bit more about the checking on $ POST[rsquoajaxrsquo] The [CGridView]widget has a very nice feature that its sorting pagination and deletion operations are alldone in AJAX mode by default That means the whole page does not get reloaded if anyof the above operations is performed However it is also possible that the widget runs innon-AJAX mode (by setting its ajaxUpdate property to be false or disabling JavaScript onthe client side) It is necessary for the delete action to differentiate these two scenarios ifthe delete request is made via AJAX we should not redirect the user browser otherwisewe should

Deleting a post should also cause the deletion of all comments for that post In additionwe should also update the tbl tag table regarding the tags for the deleted post Both ofthese tasks can be achieved by writing an afterDelete method in the Post model class asfollows

protected function afterDelete()

parentafterDelete()

Commentmodel()-gtdeleteAll(rsquopost id=rsquo$this-gtid)

Tagmodel()-gtupdateFrequency($this-gttags rsquorsquo)

The above code is very straightforward it first deletes all those comments whose post id

is the same as the ID of the deleted post it then updates the tbl tag table for the tags

of the deleted post

34 Managing Posts 31

Tip We have to explicitly delete all comments for the deleted post here becauseSQLite does not really support foreign key constraints In a DBMS that supportsthis constraint (such as MySQL PostgreSQL) the foreign key constraint can be setup such that the DBMS automatically deletes the related comments if the post isdeleted In that case we no longer this explicit deletion call in our code

32 3 Post Management

Chapter 4

Comment Management

41 Customizing Comment Model

For the Comment model we mainly need to customize the rules() and attributeLabels()

methods The attributeLabels() method returns a mapping between attribute namesand attribute labels We do not need to touch relations() since the code generated bythe yiic tool is good enough

411 Customizing rules() Method

We first customize the validation rules generated by the yiic tool The following rules areused for comments

public function rules()

return array(

array(rsquocontent author emailrsquo rsquorequiredrsquo)

array(rsquoauthor email urlrsquo rsquolengthrsquo rsquomaxrsquo=gt128)

array(rsquoemailrsquorsquoemailrsquo)

array(rsquourlrsquorsquourlrsquo)

)

In the above we specify that the author email and content attributes are required thelength of author email and url cannot exceed 128 the email attribute must be a validemail address and the url attribute must be a valid URL

412 Customizing attributeLabels() Method

We then customize the attributeLabels() method to declare the label display for eachmodel attribute This method returns an array consisting of name-label pairs When wecall CHtmlactiveLabel() to display an attribute label

34 4 Comment Management

public function attributeLabels()

return array(

rsquoidrsquo =gt rsquoIdrsquo

rsquocontentrsquo =gt rsquoCommentrsquo

rsquostatusrsquo =gt rsquoStatusrsquo

rsquocreate timersquo =gt rsquoCreate Timersquo

rsquoauthorrsquo =gt rsquoNamersquo

rsquoemailrsquo =gt rsquoEmailrsquo

rsquourlrsquo =gt rsquoWebsitersquo

rsquopost idrsquo =gt rsquoPostrsquo

)

Tip If the label for an attribute is not declared in attributeLabels() an algo-rithm will be used to generate an appropriate label For example a label VerifyCode will be generated for attributes verify code or verifyCode

413 Customizing Saving Process

Because we want to record the creation time of a comment we override the beforeSave()

method of Comment like we do for the Post model

protected function beforeSave()

if(parentbeforeSave())

if($this-gtisNewRecord)

$this-gtcreate time=time()

return true

else

return false

42 Creating and Displaying Comments

In this section we implement the comment display and creation features

421 Displaying Comments

Instead of displaying and creating comments on individual pages we use the post detailpage (generated by the view action of PostController) Below the post content display

42 Creating and Displaying Comments 35

we display first a list of comments belonging to that post and then a comment creationform

In order to display comments on the post detail page we modify the view script wwwrootblogprotectedviewspostviewphp as follows

ltdiv id=commentsgt

ltphp if($model-gtcommentCountgt=1) gt

lth3gt

ltphp echo $model-gtcommentCount rsquocomment(s)rsquo gt

lth3gt

ltphp $this-gtrenderPartial(rsquo commentsrsquoarray(

rsquopostrsquo=gt$model

rsquocommentsrsquo=gt$model-gtcomments

)) gt

ltphp endif gt

ltdivgt

In the above we call renderPartial() to render a partial view named comments to displaythe list of comments belonging to the current post Note that in the view we use theexpression $model-gtcomments to retrieve the comments for the post This is valid becausewe have declared a comments relation in the Post class Evaluating this expression wouldtrigger an implicit JOIN database query to bring back the proper comments This featureis known as lazy relational query

The partial view comments is not very interesting It mainly goes through every commentand displays the detail of it Interested readers may refer to wwwrootyiidemosblog

protectedpost commentsphp

422 Creating Comments

To handle comment creation we first modify the actionView() method of PostController

as follows

public function actionView()

$post=$this-gtloadModel()

$comment=$this-gtnewComment($post)

$this-gtrender(rsquoviewrsquoarray(

rsquomodelrsquo=gt$post

rsquocommentrsquo=gt$comment

))

36 4 Comment Management

protected function newComment($post)

$comment=new Comment

if(isset($ POST[rsquoCommentrsquo]))

$comment-gtattributes=$ POST[rsquoCommentrsquo]

if($post-gtaddComment($comment))

if($comment-gtstatus==CommentSTATUS PENDING)

Yiiapp()-gtuser-gtsetFlash(rsquocommentSubmittedrsquorsquoThank yoursquo)

$this-gtrefresh()

return $comment

In the above we call the newComment() method before we render view In the newComment()

method we generate a Comment instance and check if the comment form is submitted If sowe try to add the comment for the post by calling $post-gtaddComment($comment) If it goesthrough we refresh the post detail page In case the comment needs to be approved we willshow a flash message to indicate this decision A flash message is usually a confirmationmessage displayed to end users If the user clicks on the refresh button of his browser themessage will disappear

We also need to modify wwwrootblogprotectedviewspostviewphp furthermore

ltdiv id=commentsgt

lth3gtLeave a Commentlth3gt

ltphp if(Yiiapp()-gtuser-gthasFlash(rsquocommentSubmittedrsquo)) gt

ltdiv class=successgt

ltphp echo Yiiapp()-gtuser-gtgetFlash(rsquocommentSubmittedrsquo) gt

ltdivgt

ltphp else gt

ltphp $this-gtrenderPartial(rsquocomment formrsquoarray(

rsquomodelrsquo=gt$comment

)) gt

ltphp endif gt

ltdivgtlt-- comments --gt

43 Managing Comments 37

In the above code we display the flash message if it is available If not we displaythe comment input form by rendering the partial view wwwrootblogprotectedviews

comment formphp

43 Managing Comments

Comment management includes updating deleting and approving comments These op-erations are implemented as actions in the CommentController class

431 Updating and Deleting Comments

The code generated by yiic for updating and deleting comments remains largely un-changed

432 Approving Comments

When comments are newly created they are in pending approval status and need to beapproved in order to be visible to guest users Approving a comment is mainly aboutchanging the status column of the comment

We create an actionApprove() method in CommentController as follows

public function actionApprove()

if(Yiiapp()-gtrequest-gtisPostRequest)

$comment=$this-gtloadModel()

$comment-gtapprove()

$this-gtredirect(array(rsquoindexrsquo))

else

throw new CHttpException(400rsquoInvalid requestrsquo)

In the above when the approve action is invoked via a POST request we call the approve()

method defined in the Comment model to change the status We then redirect the userbrowser to the page displaying the post that this comment belongs to

We also modify the actionIndex() method of Comment to show all comments We wouldlike to see comments pending approved to show up first

public function actionIndex()

38 4 Comment Management

$dataProvider=new CActiveDataProvider(rsquoCommentrsquo array(

rsquocriteriarsquo=gtarray(

rsquowithrsquo=gtrsquopostrsquo

rsquoorderrsquo=gtrsquotstatus tcreate time DESCrsquo

)

))

$this-gtrender(rsquoindexrsquoarray(

rsquodataProviderrsquo=gt$dataProvider

))

Notice that in the above code because both tbl post and tbl comment have columns statusand create time we need to disambiguate the corresponding column reference by prefixingthem with table alias names As described in the guide the alias for the primary table ina relational query is always t Therefore we are prefixing t to the status and create time

columns in the above code

Like the post index view the index view for CommentController uses [CListView] to displaythe comment list which in turn uses the partial view wwwrootblogprotectedviews

comment viewphp to display the detail of each individual comment We will not go intodetails here Interested readers may refer to the corresponding file in the blog demowwwrootyiidemosblogprotectedviewscomment viewphp

Chapter 5

Portlets

51 Customizing Page Layout

Features like rdquothe most recent commentsrdquo rdquotag cloudrdquo are better to be implemented inportlets A portlet is a pluggable user interface component that renders a fragment ofHTML code In this section we describe how to set up the portlet architecture for ourblog application

Before we go into details about developing portlets we first need to adjust our page layoutso that the portlets can be placed in a side bar

A page layout determines how to share common sections (eg header footer) amongdifferent pages and how to embed contents that are specific for different pages In Yiia page layout is represented as a view under the protectedviewslayouts folder Whenwe call the render() method in a controller the rendering result will be automaticallyembedded into this layout view before it is displayed to end users The default layoutview is named as main which means the wwwrootblogprotectedviewslayoutsmain

php file in our blog application

We could directly modify the main layout to add a side bar However doing so wouldmake some irrelevant pages such as about contact pages to have a side bar which is notalways desirable For this reason we need different layouts for different pages

We thus create two different layouts column1 and column2 The former displays the pagein a single column while the latter displays two columns in the page one of which is theside bar We will use column2 layout for the post and comment pages and column1 forthe rest of the pages Because both column1 and column2 still share the same header andfooter sections we let them to share the main layout

Based on the above description we create the column1 layout as follows

ltphp $this-gtbeginContent(rsquolayoutsmainrsquo) gt

ltdiv class=containergt

40 5 Portlets

ltdiv id=contentgt

ltphp echo $content gt

ltdivgtlt-- content --gt

ltdivgt

ltphp $this-gtendContent() gt

and the column2 layout as follows

ltphp $this-gtbeginContent(rsquolayoutsmainrsquo) gt

ltdiv class=containergt

ltdiv class=span-18gt

ltdiv id=contentgt

ltphp echo $content gt

ltdivgtlt-- content --gt

ltdivgt

ltdiv class=span-6 lastgt

ltdiv id=sidebargt

portlets to be placed here

ltdivgtlt-- sidebar --gt

ltdivgt

ltdivgt

ltphp $this-gtendContent() gt

As we can see both column1 and column2 are enclosed in a pair of beginContent andendContent calls They essentially use the specified view layoutsmain to decorate theenclosed content In all these layout views the variable $content refers to the dynamiccontent to be embedded into the layout

Besides customizing the layout view file we also need to adjust the CSS file wwwroot

blogcssmaincss so that the overall appearance would look like what we see in the blogdemo We will not go into details here

Info The skeleton application generated by the yiic tool uses the BluePrint CSSframework It provides a nice font reset and a robust grid system For more detailson how to use this framework please refer to its documentation

After creating the new layouts we need to set the layout property of controllers to makeuse of them So far we have three controllers SiteController PostController andCommentController We set SiteController to use column1 as the default layout whilethe rest two of the controllers use column2 for example

class PostController extends Controller

52 Creating User Menu Portlet 41

public $layout=rsquocolumn2rsquo

Tip If some view of the controller does not use the same layout as declaredby the layout property it may customize it in the view by a simple assignment$this-gtlayout=rsquonewlayoutrsquo

52 Creating User Menu Portlet

Based on the requirements analysis we need three different portlets the rdquouser menurdquoportlet the rdquotag cloudrdquo portlet and the rdquorecent commentsrdquo portlet We will develop theseportlets by extending the [CPortlet] widget provided by Yii

In this section we will develop our first concrete portlet - the user menu portlet whichdisplays a list of menu items that are only available to authenticated users The menucontains four items

bull Approve Comments a hyperlink that leads to a list of comments pending approval

bull Create New Post a hyperlink that leads to the post creation page

bull Manage Posts a hyperlink that leads to the post management page

bull Logout a link button that would log out the current user

521 Creating UserMenu Class

We create the UserMenu class to represent the logic part of the user menu portlet Theclass is saved in the file wwwrootblogprotectedcomponentsUserMenuphp which has thefollowing content

Yiiimport(rsquoziiwidgetsCPortletrsquo)

class UserMenu extends CPortlet

public function init()

$this-gttitle=CHtmlencode(Yiiapp()-gtuser-gtname)

parentinit()

42 5 Portlets

protected function renderContent()

$this-gtrender(rsquouserMenursquo)

The UserMenu class extends from the CPortlet class that we created previously It overridesboth the init() method and the renderContent() method of CPortlet The former setsthe portlet title to be the name of the current user the latter generates the portlet bodycontent by rendering a view named userMenu

Tip Notice that we have to explicitly include the CPortlet class by calling Yii

import() before we refer to it the first time This is because CPortlet is part of thezii project ndash the official extension library for Yii For performance considerationclasses in this project are not listed as core classes Therefore we have to import itbefore we use it the first time

522 Creating userMenu View

Next we create the userMenu view which is saved in the file wwwrootblogprotected

componentsviewsuserMenuphp

ltulgt

ltligtltphp echo CHtmllink(rsquoCreate New Postrsquoarray(rsquopostcreatersquo)) gtltligt

ltligtltphp echo CHtmllink(rsquoManage Postsrsquoarray(rsquopostadminrsquo)) gtltligt

ltligtltphp echo CHtmllink(rsquoApprove Commentsrsquoarray(rsquocommentindexrsquo))

rsquo (rsquo Commentmodel()-gtpendingCommentCount rsquo)rsquo gtltligt

ltligtltphp echo CHtmllink(rsquoLogoutrsquoarray(rsquositelogoutrsquo)) gtltligt

ltulgt

Info By default view files for a widget should be placed under the views sub-directory of the directory containing the widget class file The file name must bethe same as the view name

523 Using UserMenu Portlet

It is time for us to make use of our newly completed UserMenu portlet We modify thelayout view file wwwrootblogprotectedviewslayoutscolumn2php as follows

53 Creating Tag Cloud Portlet 43

ltdiv id=sidebargt

ltphp if(Yiiapp()-gtuser-gtisGuest) $this-gtwidget(rsquoUserMenursquo) gt

ltdivgt

In the above we call the widget() method to generate and execute an instance of theUserMenu class Because the portlet should only be displayed to authenticated users weonly call widget() when the isGuest property of the current user is false (meaning theuser is authenticated)

524 Testing UserMenu Portlet

Letrsquos test what we have so far

1 Open a browser window and enter the URL httpwwwexamplecomblogindex

php Verify that there is nothing displayed in the side bar section of the page

2 Click on the Login hyperlink and fill out the login form to login If successful verifythat the UserMenu portlet appears in the side bar and the portlet has the usernameas its title

3 Click on the rsquoLogoutrsquo hyperlink in the UserMenu portlet Verify that the logout actionis successful and the UserMenu portlet disappears

525 Summary

What we have created is a portlet that is highly reusable We can easily reuse it in adifferent project with little or no modification Moreover the design of this portlet followsclosely the philosophy that logic and presentation should be separated While we did notpoint this out in the previous sections such practice is used nearly everywhere in a typicalYii application

53 Creating Tag Cloud Portlet

Tag cloud displays a list of post tags with visual decorations hinting the popularity of eachindividual tag

531 Creating TagCloud Class

We create the TagCloud class in the file wwwrootblogprotectedcomponentsTagCloud

php The file has the following content

44 5 Portlets

Yiiimport(rsquoziiwidgetsCPortletrsquo)

class TagCloud extends CPortlet

public $title=rsquoTagsrsquo

public $maxTags=20

protected function renderContent()

$tags=Tagmodel()-gtfindTagWeights($this-gtmaxTags)

foreach($tags as $tag=gt$weight)

$link=CHtmllink(CHtmlencode($tag) array(rsquopostindexrsquorsquotagrsquo=gt$tag))

echo CHtmltag(rsquospanrsquo array(

rsquoclassrsquo=gtrsquotagrsquo

rsquostylersquo=gtfont-size$weightpt) $link)n

Unlike the UserMenu portlet the TagCloud portlet does not use a view Instead its presen-tation is done in the renderContent() method This is because the presentation does notcontain much HTML tags

We display each tag as a hyperlink to the post index page with the corresponding tagparameter The font size of each tag link is adjusted according to their relative weightamong other tags If a tag has higher frequency value than the other it will have a biggerfont size

532 Using TagCloud Portlet

Usage of the TagCloud portlet is very simple We modify the layout file wwwrootblog

protectedviewslayoutsmainphp as follows

ltdiv id=sidebargt

ltphp if(Yiiapp()-gtuser-gtisGuest) $this-gtwidget(rsquoUserMenursquo) gt

ltphp $this-gtwidget(rsquoTagCloudrsquo array(

rsquomaxTagsrsquo=gtYiiapp()-gtparams[rsquotagCloudCountrsquo]

)) gt

ltdivgt

54 Creating Recent Comments Portlet 45

54 Creating Recent Comments Portlet

In this section we create the last portlet that displays a list of comments recently pub-lished

541 Creating RecentComments Class

We create the RecentComments class in the file wwwrootblogprotectedcomponentsRecentCommentsphp The file has the following content

Yiiimport(rsquoziiwidgetsCPortletrsquo)

class RecentComments extends CPortlet

public $title=rsquoRecent Commentsrsquo

public $maxComments=10

public function getRecentComments()

return Commentmodel()-gtfindRecentComments($this-gtmaxComments)

protected function renderContent()

$this-gtrender(rsquorecentCommentsrsquo)

In the above we invoke the findRecentComments method which is defined in the Comment

class as follows

class Comment extends CActiveRecord

public function findRecentComments($limit=10)

return $this-gtwith(rsquopostrsquo)-gtfindAll(array(

rsquoconditionrsquo=gtrsquotstatus=rsquoselfSTATUS APPROVED

rsquoorderrsquo=gtrsquotcreate time DESCrsquo

rsquolimitrsquo=gt$limit

))

46 5 Portlets

542 Creating recentComments View

The recentComments view is saved in the file wwwrootblogprotectedcomponentsviews

recentCommentsphp It simply displays every comment returned by the RecentComments

getRecentComments() method

543 Using RecentComments Portlet

We modify the layout file wwwrootblogprotectedviewslayoutsmainphp to embedthis last portlet

ltdiv id=sidebargt

ltphp if(Yiiapp()-gtuser-gtisGuest) $this-gtwidget(rsquoUserMenursquo) gt

ltphp $this-gtwidget(rsquoTagCloudrsquo array(

rsquomaxTagsrsquo=gtYiiapp()-gtparams[rsquotagCloudCountrsquo]

)) gt

ltphp $this-gtwidget(rsquoRecentCommentsrsquo array(

rsquomaxCommentsrsquo=gtYiiapp()-gtparams[rsquorecentCommentCountrsquo]

)) gt

ltdivgt

Chapter 6

Final Work

61 Beautifying URLs

The URLs linking various pages of our blog application currently look ugly For examplethe URL for the page showing a post looks like the following

indexphpr=postshowampid=1

In this section we describe how to beautify these URLs and make them SEO-friendlyOur goal is to be able to use the following URLs in the application

1 indexphppostsyii leads to the page showing a list of posts with tag yii

2 indexphppost2A+Test+Post leads to the page showing the detail of the postwith ID 2 whose title is A Test Post

3 indexphppostupdate1 leads to the page that allows updating the post with ID1

Note that in the second URL format we include the post title in the URL This is mainlyto make the URL SEO friendly It is said that search engines may also respect the wordsfound in a URL when it is being indexed

To achieve our goal we modify the application configuration as follows

return array(

rsquocomponentsrsquo=gtarray(

rsquourlManagerrsquo=gtarray(

rsquourlFormatrsquo=gtrsquopathrsquo

rsquorulesrsquo=gtarray(

48 6 Final Work

rsquopostltidd+gtlttitlegtrsquo=gtrsquopostviewrsquorsquopostslttaggtrsquo=gtrsquopostindexrsquo

)

)

)

)

In the above we configure the urlManager component by setting its urlFormat propertyto be path and adding a set of rules

The rules are used by urlManager to parse and create the URLs in the desired formatFor example the first rule says that if a URL indexphppostsyii is requested theurlManager component should be responsible to dispatch the request to the route post

index and generate a tag GET parameter with the value yii On the other hand whencreating a URL with the route postindex and parameter tag the urlManager componentwill also use this rule to generate the desired URL indexphppostsyii For this reasonwe say that urlManager is a two-way URL manager

The urlManager component can further beautify our URLs such as hiding indexphp inthe URLs appending suffix like html to the URLs We can obtain these features easilyby configuring various properties of urlManager in the application configuration For moredetails please refer to the Guide

62 Logging Errors

A production Web application often needs sophisticated logging for various events In ourblog application we would like to log the errors occurring when it is being used Sucherrors could be programming mistakes or usersrsquo misuse of the system Logging these errorswill help us to improve the blog application

We enable the error logging by modifying the application configuration as follows

return array(

rsquopreloadrsquo=gtarray(rsquologrsquo)

rsquocomponentsrsquo=gtarray(

rsquologrsquo=gtarray(

rsquoclassrsquo=gtrsquoCLogRouterrsquo

rsquoroutesrsquo=gtarray(

array(

rsquoclassrsquo=gtrsquoCFileLogRoutersquo

rsquolevelsrsquo=gtrsquoerror warningrsquo

63 Final Tune-up and Deployment 49

)

)

)

)

)

With the above configuration if an error or warning occurs detailed information will belogged and saved in a file located under the directory wwwrootblogprotectedruntime

The log component offers more advanced features such as sending log messages to a listof email addresses displaying log messages in JavaScript console window etc For moredetails please refer to the Guide

63 Final Tune-up and Deployment

We are close to finish our blog application Before deployment we would like to do sometune-ups

631 Changing Home Page

We change to use the post list page as the home page We modify the application config-uration as follows

return array(

rsquodefaultControllerrsquo=gtrsquopostrsquo

)

Tip Because PostController already declares list to be its default action whenwe access the home page of the application we will see the result generated by thelist action of the post controller

632 Enabling Schema Caching

Because ActiveRecord relies on the metadata about tables to determine the column infor-mation it takes time to read the metadata and analyze it This may not be a problemduring development stage but for an application running in production mode it is a totalwaste of time if the database schema does not change Therefore we should enable theschema caching by modifying the application configuration as follows

50 6 Final Work

return array(

rsquocomponentsrsquo=gtarray(

rsquocachersquo=gtarray(

rsquoclassrsquo=gtrsquoCDbCachersquo

)

rsquodbrsquo=gtarray(

rsquoclassrsquo=gtrsquosystemdbCDbConnectionrsquo

rsquoconnectionStringrsquo=gtrsquosqlitewwwrootblogprotecteddatablogdbrsquo

rsquoschemaCachingDurationrsquo=gt3600

)

)

)

In the above we first add a cache component which uses a default SQLite database as thecaching storage If our server is equipped with other caching extensions such as APCwe could change to use them as well We also modify the db component by setting itsschemaCachingDuration property to be 3600 which means the parsed database schemadata can remain valid in cache for 3600 seconds

633 Disabling Debugging Mode

We modify the entry script file wwwrootblogindexphp by removing the line definingthe constant YII DEBUG This constant is useful during development stage because it allowsYii to display more debugging information when an error occurs However when theapplication is running in production mode displaying debugging information is not agood idea because it may contain sensitive information such as where the script file islocated and the content in the file etc

634 Deploying the Application

The final deployment process manly involves copying the directory wwwrootblog to thetarget directory The following checklist shows every needed step

1 Install Yii in the target place if it is not available

2 Copy the entire directory wwwrootblog to the target place

3 Edit the entry script file indexphp by pointing the $yii variable to the new Yiibootstrap file

4 Edit the file protectedyiicphp by setting the $yiic variable to be the new Yiiyiicphp file

64 Future Enhancements 51

5 Change the permission of the directories assets and protectedruntime so that theyare writable by the Web server process

64 Future Enhancements

641 Using a Theme

Without writing any code our blog application is already themeable To use a themewe mainly need to develop the theme by writing customized view files in the theme Forexample to use a theme named classic that uses a different page layout we would createa layout view file wwwrootblogthemesclassicviewslayoutsmainphp We also needto change the application configuration to indicate our choice of the classic theme

return array(

rsquothemersquo=gtrsquoclassicrsquo

)

642 Internationalization

We may also internationalize our blog application so that its pages can be displayed indifferent languages This mainly involves efforts in two aspects

First we may create view files in different languages For example for the list pageof PostController we can create a view file wwwrootblogprotectedviewspostzh cn

listphp When the application is configured to use simplified Chinese (the language codeis zh cn) Yii will automatically use this new view file instead of the original one

Second we may create message translations for those messages generated by code Themessage translations should be saved as files under the directory wwwrootblogprotected

messages We also need to modify the code where we use text strings by enclosing themin the method call Yiit()

For more details about internationalization please refer to the Guide

643 Improving Performance with Cache

While the Yii framework itself is very efficient it is not necessarily true that an applicationwritten in Yii efficient There are several places in our blog application that we can improvethe performance For example the tag clould portlet could be one of the performancebottlenecks because it involves complex database query and PHP logic

52 6 Final Work

We can make use of the sophisticated caching feature provided by Yii to improve theperformance One of the most useful components in Yii is COutputCache which caches afragment of page display so that the underlying code generating the fragment does not needto be executed for every request For example in the layout file wwwrootblogprotected

viewslayoutsmainphp we can enclose the tag cloud portlet with COutputCache

ltphp if($this-gtbeginCache(rsquotagCloudrsquo array(rsquodurationrsquo=gt3600))) gt

ltphp $this-gtwidget(rsquoTagCloudrsquo array(

rsquomaxTagsrsquo=gtYiiapp()-gtparams[rsquotagCloudCountrsquo]

)) gt

ltphp $this-gtendCache() gt

With the above code the tag cloud display will be served from cache instead of beinggenerated on-the-fly for every request The cached content will remain valid in cache for3600 seconds

644 Adding New Features

Our blog application only has very basic functionalities To become a complete blogsystem more features are needed for example calendar portlet email notifications postcategorization archived post portlet and so on We will leave the implementation of thesefeatures to interested readers

  • Contents
  • License
  • Getting Started
    • Building a Blog System using Yii
    • Testdriving with Yii
      • Installing Yii
      • Creating Skeleton Application
      • Application Workflow
        • Requirements Analysis
        • Overall Design
          • Initial Prototyping
            • Setting Up Database
              • Creating Database
              • Establishing Database Connection
                • Scaffolding
                • Authenticating User
                • Summary
                  • Post Management
                    • Customizing Post Model
                      • Customizing rules() Method
                      • Customizing relations() Method
                      • Representing Status in Text
                        • Creating and Updating Posts
                          • Customizing Access Control
                          • Customizing create and update Operations
                            • Displaying Posts
                              • Customizing view Operation
                              • Customizing index Operation
                                • Managing Posts
                                  • Listing Posts in Tabular View
                                  • Deleting Posts
                                      • Comment Management
                                        • Customizing Comment Model
                                          • Customizing rules() Method
                                          • Customizing attributeLabels() Method
                                          • Customizing Saving Process
                                            • Creating and Displaying Comments
                                              • Displaying Comments
                                              • Creating Comments
                                                • Managing Comments
                                                  • Updating and Deleting Comments
                                                  • Approving Comments
                                                      • Portlets
                                                        • Customizing Page Layout
                                                        • Creating User Menu Portlet
                                                          • Creating UserMenu Class
                                                          • Creating userMenu View
                                                          • Using UserMenu Portlet
                                                          • Testing UserMenu Portlet
                                                          • Summary
                                                            • Creating Tag Cloud Portlet
                                                              • Creating TagCloud Class
                                                              • Using TagCloud Portlet
                                                                • Creating Recent Comments Portlet
                                                                  • Creating RecentComments Class
                                                                  • Creating recentComments View
                                                                  • Using RecentComments Portlet
                                                                      • Final Work
                                                                        • Beautifying URLs
                                                                        • Logging Errors
                                                                        • Final Tune-up and Deployment
                                                                          • Changing Home Page
                                                                          • Enabling Schema Caching
                                                                          • Disabling Debugging Mode
                                                                          • Deploying the Application
                                                                            • Future Enhancements
                                                                              • Using a Theme
                                                                              • Internationalization
                                                                              • Improving Performance with Cache
                                                                              • Adding New Features
Page 26: Yii Blog 1.1.0 Demo Tutor

Chapter 3

Post Management

31 Customizing Post Model

The Post model class generated by the yiic tool mainly needs to be modified in two places

bull the rules() method specifies the validation rules for the model attributes

bull the relations() method specifies the related objects

Info A model consists of a list of attributes each associated with a column in thecorresponding database table Attributes can be declared explicitly as class membervariables or implicitly without any declaration

311 Customizing rules() Method

We first specify the validation rules which ensure the attribute values entered by usersare correct before they are saved to the database For example the status attribute ofPost should be an integer 1 2 or 3 The yiic tool also generates validation rules for eachmodel However these rules are based on the table column information and may not beappropriate

Based on the requirement analysis we modify the rules() method as follows

public function rules()

return array(

array(rsquotitle content statusrsquo rsquorequiredrsquo)

array(rsquotitlersquo rsquolengthrsquo rsquomaxrsquo=gt128)

array(rsquostatusrsquo rsquoinrsquo rsquorangersquo=gtarray(123))

array(rsquotagsrsquo rsquomatchrsquo rsquopatternrsquo=gtrsquo^[ws]+$rsquorsquomessagersquo=gtrsquoTags can only contain word charactersrsquo)

20 3 Post Management

array(rsquotagsrsquo rsquonormalizeTagsrsquo)

)

In the above we specify that the title content and status attributes are required thelength of title should not exceed 128 the status attribute value should be 1 (draft) 2(published) or 3 (archived) and the tags attribute should only contain word charactersand commas In addition we use normalizeTags to normalize the user-entered tags so thatthe tags are unique and properly separated with commas

The validators such as required length in and match are all built-in validators providedby Yii The normalizeTags validator is a method-based validator that we need to definein the Post class For more information about how to specify validation rules please referto the Guide

public function normalizeTags($attribute$params)

$this-gttags=Tagarray2string(array unique(Tagstring2array($this-gttags)))

The rules declared in this method are executed one by one when we call the validate() orsave() method of the model instance

Note It is very important to remember that attributes appearing in rules() mustbe those to be entered by end users Other attributes such as id and create time

in the Post model which are set by our code or database should not be in rules()For more details please refer to Securing Attribute Assignments

After making these changes we can visit the post creation page again to verify that thenew validation rules are taking effect

312 Customizing relations() Method

Lastly we customize the relations() method to specify the related objects of a post Bydeclaring these related objects in relations() we can exploit the powerful RelationalActiveRecord (RAR) feature to access the related object information of a post such asits author and comments without the need to write complex SQL JOIN statements

We customize the relations() method as follows

31 Customizing Post Model 21

public function relations()

return array(

rsquoauthorrsquo =gt array(selfBELONGS TO rsquoUserrsquo rsquoauthor idrsquo)

rsquocommentsrsquo =gt array(selfHAS MANY rsquoCommentrsquo rsquopost idrsquo

rsquoconditionrsquo=gtrsquocommentsstatus=rsquoCommentSTATUS APPROVED

rsquoorderrsquo=gtrsquocommentscreate time DESCrsquo)

rsquocommentCountrsquo =gt array(selfSTAT rsquoCommentrsquo rsquopost idrsquo

rsquoconditionrsquo=gtrsquostatus=rsquoCommentSTATUS APPROVED)

)

The above relations state that

bull A post belongs to an author whose class is User and the relationship is establishedbased on the author id attribute value of the post

bull A post has many comments whose class is Comment and the relationship is establishedbased on the post id attribute value of the comments These comments should besorted according to their creation time and the comments must be approved

bull The commentCount relation is a bit special as it returns back an aggregation resultwhich is about how many comments the post has

With the above relation declaration we can easily access the author and comments of apost like the following

$author=$post-gtauthor

echo $author-gtusername

$comments=$post-gtcomments

foreach($comments as $comment)

echo $comment-gtcontent

For more details about how to declare and use relations please refer to the Guide

313 Representing Status in Text

Because the status of a post is stored as an integer in the database we need to provide atextual representation so that it is more intuitive when being displayed to end users In alarge system the similar requirement is very common

22 3 Post Management

As a generic solution we use the tbl lookup table to store the mapping between integervalues and textual representations that are needed by other data objects We modify theLookup model class as follows to more easily access the textual data in the table

class Lookup extends CActiveRecord

private static $ items=array()

public static function items($type)

if(isset(self$ items[$type]))

selfloadItems($type)

return self$ items[$type]

public static function item($type$code)

if(isset(self$ items[$type]))

selfloadItems($type)

return isset(self$ items[$type][$code]) self$ items[$type][$code] false

private static function loadItems($type)

self$ items[$type]=array()

$models=selfmodel()-gtfindAll(array(

rsquoconditionrsquo=gtrsquotype=typersquo

rsquoparamsrsquo=gtarray(rsquotypersquo=gt$type)

rsquoorderrsquo=gtrsquopositionrsquo

))

foreach($models as $model)

self$ items[$type][$model-gtcode]=$model-gtname

Our new code mainly provides two static methods Lookupitems() and Lookupitem()The former returns a list of strings belonging to the specified data type while the latterreturns a particular string for the given data type and data value

Our blog database is pre-populated with two lookup types PostStatus and CommentStatusThe former refers to the possible post statuses while the latter the comment statuses

In order to make our code easier to read we also declare a set of constants to representthe status integer values We should use these constants through our code when referringto the corresponding status values

32 Creating and Updating Posts 23

class Post extends CActiveRecord

const STATUS DRAFT=0

const STATUS PUBLISHED=1

const STATUS ARCHIVED=2

Therefore we can call Lookupitems(rsquoPostStatusrsquo) to get the list of possible post statuses(text strings indexed by the corresponding integer values) and call Lookupitem(rsquoPostStatusrsquoPostSTATUS PUBLISHED) to get the string representation of the published status

32 Creating and Updating Posts

With the Post model ready we need to fine-tune the actions and views for the controllerPostController In this section we first customize the access control of CRUD operationswe then modify the code implementing the create and update operations

321 Customizing Access Control

The first thing we want to do is to customize the access control because the code generatedby yiic does not fit our needs

We modify the accessRules() method in the file wwwrootblogprotectedcontrollers

PostControllerphp as follows

public function accessRules()

return array(

array(rsquoallowrsquo allow all users to perform rsquolistrsquo and rsquoshowrsquo actions

rsquoactionsrsquo=gtarray(rsquoindexrsquo rsquoviewrsquo)

rsquousersrsquo=gtarray(rsquorsquo)

)

array(rsquoallowrsquo allow authenticated users to perform any action

rsquousersrsquo=gtarray(rsquorsquo)

)

array(rsquodenyrsquo deny all users

rsquousersrsquo=gtarray(rsquorsquo)

)

)

The above rules state that all users can access the index and view actions and authenti-cated users can access any actions including the admin action The user should be denied

24 3 Post Management

access in any other scenario Note that these rules are evaluated in the order they arelisted here The first rule matching the current context makes the access decision Forexample if the current user is the system owner who tries to visit the post creation pagethe second rule will match and it will give the access to the user

322 Customizing create and update Operations

The create and update operations are very similar They both need to display an HTMLform to collect user inputs validate them and save them into database The main differ-ence is that the update operation will pre-populate the form with the existing post datafound in the database For this reason the yiic tool generates a partial view wwwroot

blogprotectedviewspost formphp that is embedded in both the create and update

views to render the needed HTML form

We first change the formphp file so that the HTML form only collects the inputs wewant title content tags and status We use plain text fields to collect inputs for thefirst three attributes and a dropdown list to collect input for status The dropdown listoptions are the text displays of the possible post statuses

ltphp echo CHtmlactiveDropDownList($postrsquostatusrsquoLookupitems(rsquoPostStatusrsquo)) gt

In the above we call Lookupitems(rsquoPostStatusrsquo) to bring back the list of post statuses

We then modify the Post class so that it can automatically set some attributes (eg create

time author id) before a post is saved to the database We override the beforeSave()

method as follows

protected function beforeSave()

if(parentbeforeSave())

if($this-gtisNewRecord)

$this-gtcreate time=$this-gtupdate time=time()

$this-gtauthor id=Yiiapp()-gtuser-gtid

else

$this-gtupdate time=time()

return true

else

return false

33 Displaying Posts 25

When we save a post we want to update the tbl tag table to reflect the change of tagfrequencies We can do this work in the afterSave() method which is automaticallyinvoked by Yii after a post is successfully saved into the database

protected function afterSave()

parentafterSave()

Tagmodel()-gtupdateFrequency($this-gt oldTags $this-gttags)

private $ oldTags

protected function afterFind()

parentafterFind()

$this-gt oldTags=$this-gttags

In the implementation because we want to detect if the user changes the tags in case he isupdating an existing post we need to know what the old tags are For this reason we alsowrite the afterFind() method to keep the old tags in the variable oldTags The methodafterFind() is invoked automatically by Yii when an AR record is populated with thedata from database

We are not going to give details of the TagupdateFrequency() method here Interestedreaders may refer to the file wwwrootyiidemosblogprotectedmodelsTagphp

33 Displaying Posts

In our blog application a post may be displayed among a list of posts or by itself Theformer is implemented as the index operation while the latter the view operation In thissection we customize both operations to fulfill our initial requirements

331 Customizing view Operation

The view operation is implemented by the actionView() method in PostController Itsdisplay is generated by the view view with the view file wwwrootblogprotectedviews

postviewphp

Below is the relevant code implementing the view operation in PostController

public function actionView()

26 3 Post Management

$this-gtrender(rsquoviewrsquoarray(

$post=$this-gtloadModel()

$this-gtrender(rsquoviewrsquoarray(

rsquomodelrsquo=gt$post

))

))

private $ model

public function loadModel()

if($this-gt model===null)

if(isset($ GET[rsquoidrsquo]))

if(Yiiapp()-gtuser-gtisGuest)

$condition=rsquostatus=rsquoPostSTATUS PUBLISHED

rsquo OR status=rsquoPOSTSTATUS ARCHIVED

else

$condition=rsquorsquo

$this-gt model=Postmodel()-gtfindbyPk($ GET[rsquoidrsquo] $condition)

if($this-gt model===null)

throw new CHttpException(404rsquoThe requested page does not existrsquo)

return $this-gt model

Our change mainly lies in the loadModel() method In this method we query the Post

table according to the id GET parameter If the post is not found or if it is not publishedor archived (when the user is a guest) we will throw a 404 HTTP error Otherwise thepost object is returned to actionView() which in turn passes the post object to the viewscript for further display

Tip Yii captures HTTP exceptions (instances of CHttpException) and displaysthem in either predefined templates or customized error views The skeleton appli-cation generated by yiic already contains a customized error view in wwwroot

blogprotectedviewssiteerrorphp We can modify this file if we want tofurther customize the error display

The change in the view script is mainly about ajdusting the formatting and styles of thepost display We will not go into details here Interested readers may refer to wwwroot

blogprotectedviewspostviewphp

33 Displaying Posts 27

332 Customizing index Operation

Like the view operation we customize the index operation in two places the actionIndex()

method in PostController and the view file wwwrootblogprotectedviewspostindex

php We mainly need to add the support for displaying a list of posts that are associatedwith a specified tag

Below is the modified actionIndex() method in PostController

public function actionIndex()

$criteria=new CDbCriteria(array(

rsquoconditionrsquo=gtrsquostatus=rsquoPostSTATUS PUBLISHED

rsquoorderrsquo=gtrsquoupdate time DESCrsquo

rsquowithrsquo=gtrsquocommentCountrsquo

))

if(isset($ GET[rsquotagrsquo]))

$criteria-gtaddSearchCondition(rsquotagsrsquo$ GET[rsquotagrsquo])

$dataProvider=new CActiveDataProvider(rsquoPostrsquo array(

rsquopaginationrsquo=gtarray(

rsquopageSizersquo=gtYiiapp()-gtparams[rsquopostsPerPagersquo]

)

rsquocriteriarsquo=gt$criteria

))

$this-gtrender(rsquoindexrsquoarray(

rsquodataProviderrsquo=gt$dataProvider

))

In the above we first create a query criteria for retrieving post list The criteria statesthat only published posts should be returned and they should be sorted according to theirupdate time in descending order Because when displaying a post in the list we want toshow how many comments the post has received in the criteria we also specify to bringback commentCount which if you remember is a relation declared in Postrelations()

In case when a user wants to see posts with a specific tag we would add a search conditionto the criteria to look for the specified tag

Using the query criteria we create a data provider which mainly serves for three purposesFirst it does pagination of the data when too many results may be returned Herewe customize the pagination by setting the page size to be an application parameterpostsPerPage Second it does sorting according to the user request And finally it feedsthe paginated and sorted data to widgets or view code for presentation

28 3 Post Management

After we finish with actionIndex() we modify the index view as follows Our change ismainly about adding the h1 header when the user specifies to display posts with a tag

ltphp if(empty($ GET[rsquotagrsquo])) gt

lth1gtPosts Tagged with ltigtltphp echo CHtmlencode($ GET[rsquotagrsquo]) gtltigtlth1gt

ltphp endif gt

ltphp $this-gtwidget(rsquoziiwidgetsCListViewrsquo array(

rsquodataProviderrsquo=gt$dataProvider

rsquoitemViewrsquo=gtrsquo viewrsquo

rsquotemplatersquo=gtitemsnpager)) gt

Note that in the above we use [CListView] to display the post list This widget requires apartial view to display the detail of each individual post Here we specify the partial viewto be view which means the file wwwrootblogprotectedviewspost viewphp In thisview script we can acccess the post instance being displayed via a local variable named$data

34 Managing Posts

Managing posts mainly refers to listing posts in an administrative view that allows us tosee posts with all statuses updating them and deleting them They are accomplished bythe admin operation and the delete operation respectively The code generated by yiic

does not need much modification Below we mainly explain how these two operations areimplemented

341 Listing Posts in Tabular View

The admin operation shows posts with all statuses in a tabular view The view supportssorting and pagination The following is the actionAdmin() method in PostController

public function actionAdmin()

$dataProvider=new CActiveDataProvider(rsquoPostrsquo array(

rsquosortrsquo=gtarray(

rsquodefaultOrderrsquo=gtrsquostatus update time DESCrsquo

)

))

$this-gtrender(rsquoadminrsquoarray(

rsquodataProviderrsquo=gt$dataProvider

))

34 Managing Posts 29

The above code is very similar to that in actionIndex() The main difference is in thequery criteria Here we do not specify any particular query criteria since we want to seeall posts We do customize the sort property of the data provider so that by default theposts are shown in the order of their statuses and update time

Below is the code for the admin view

lth1gtManage Postslth1gt

ltphp $this-gtwidget(rsquoziiwidgetsgridCGridViewrsquo array(

rsquodataProviderrsquo=gt$dataProvider

rsquocolumnsrsquo=gtarray(

array(

rsquonamersquo=gtrsquotitlersquo

rsquotypersquo=gtrsquorawrsquo

rsquovaluersquo=gtrsquoCHtmllink(CHtmlencode($data-gttitle) $data-gturl)rsquo

)

array(

rsquonamersquo=gtrsquostatusrsquo

rsquovaluersquo=gtrsquoLookupitem(PostStatus$data-gtstatus)rsquo

)

rsquocreate timedatetimersquo

rsquoupdate timedatetimersquo

array(

rsquoclassrsquo=gtrsquoCButtonColumnrsquo

)

)

)) gt

We use [CGridView] to display the posts It allows us to sort by a column and paginatethrough the posts if there are too many to be displayed in a single page Our change ismainly about how to display each column For example for the title column we specifythat it should be displayed as a hyperlink that points to the detailed view of the post

Tip When displaying text we call CHtmlencode() to encode HTML entities init This prevents from cross-site scripting attack

342 Deleting Posts

In the admin data grid there is a delete button in each row Clicking on the button shoulddelete the corresponding post Internally this triggers the delete action implemented asfollows

30 3 Post Management

public function actionDelete()

if(Yiiapp()-gtrequest-gtisPostRequest)

we only allow deletion via POST request

$this-gtloadModel()-gtdelete()

if(isset($ POST[rsquoajaxrsquo]))

$this-gtredirect(array(rsquoindexrsquo))

else

throw new CHttpException(400rsquoInvalid request Please do not repeat this request againrsquo)

The above code is the one generated by the yiic tool without any change We wouldlike to explain a little bit more about the checking on $ POST[rsquoajaxrsquo] The [CGridView]widget has a very nice feature that its sorting pagination and deletion operations are alldone in AJAX mode by default That means the whole page does not get reloaded if anyof the above operations is performed However it is also possible that the widget runs innon-AJAX mode (by setting its ajaxUpdate property to be false or disabling JavaScript onthe client side) It is necessary for the delete action to differentiate these two scenarios ifthe delete request is made via AJAX we should not redirect the user browser otherwisewe should

Deleting a post should also cause the deletion of all comments for that post In additionwe should also update the tbl tag table regarding the tags for the deleted post Both ofthese tasks can be achieved by writing an afterDelete method in the Post model class asfollows

protected function afterDelete()

parentafterDelete()

Commentmodel()-gtdeleteAll(rsquopost id=rsquo$this-gtid)

Tagmodel()-gtupdateFrequency($this-gttags rsquorsquo)

The above code is very straightforward it first deletes all those comments whose post id

is the same as the ID of the deleted post it then updates the tbl tag table for the tags

of the deleted post

34 Managing Posts 31

Tip We have to explicitly delete all comments for the deleted post here becauseSQLite does not really support foreign key constraints In a DBMS that supportsthis constraint (such as MySQL PostgreSQL) the foreign key constraint can be setup such that the DBMS automatically deletes the related comments if the post isdeleted In that case we no longer this explicit deletion call in our code

32 3 Post Management

Chapter 4

Comment Management

41 Customizing Comment Model

For the Comment model we mainly need to customize the rules() and attributeLabels()

methods The attributeLabels() method returns a mapping between attribute namesand attribute labels We do not need to touch relations() since the code generated bythe yiic tool is good enough

411 Customizing rules() Method

We first customize the validation rules generated by the yiic tool The following rules areused for comments

public function rules()

return array(

array(rsquocontent author emailrsquo rsquorequiredrsquo)

array(rsquoauthor email urlrsquo rsquolengthrsquo rsquomaxrsquo=gt128)

array(rsquoemailrsquorsquoemailrsquo)

array(rsquourlrsquorsquourlrsquo)

)

In the above we specify that the author email and content attributes are required thelength of author email and url cannot exceed 128 the email attribute must be a validemail address and the url attribute must be a valid URL

412 Customizing attributeLabels() Method

We then customize the attributeLabels() method to declare the label display for eachmodel attribute This method returns an array consisting of name-label pairs When wecall CHtmlactiveLabel() to display an attribute label

34 4 Comment Management

public function attributeLabels()

return array(

rsquoidrsquo =gt rsquoIdrsquo

rsquocontentrsquo =gt rsquoCommentrsquo

rsquostatusrsquo =gt rsquoStatusrsquo

rsquocreate timersquo =gt rsquoCreate Timersquo

rsquoauthorrsquo =gt rsquoNamersquo

rsquoemailrsquo =gt rsquoEmailrsquo

rsquourlrsquo =gt rsquoWebsitersquo

rsquopost idrsquo =gt rsquoPostrsquo

)

Tip If the label for an attribute is not declared in attributeLabels() an algo-rithm will be used to generate an appropriate label For example a label VerifyCode will be generated for attributes verify code or verifyCode

413 Customizing Saving Process

Because we want to record the creation time of a comment we override the beforeSave()

method of Comment like we do for the Post model

protected function beforeSave()

if(parentbeforeSave())

if($this-gtisNewRecord)

$this-gtcreate time=time()

return true

else

return false

42 Creating and Displaying Comments

In this section we implement the comment display and creation features

421 Displaying Comments

Instead of displaying and creating comments on individual pages we use the post detailpage (generated by the view action of PostController) Below the post content display

42 Creating and Displaying Comments 35

we display first a list of comments belonging to that post and then a comment creationform

In order to display comments on the post detail page we modify the view script wwwrootblogprotectedviewspostviewphp as follows

ltdiv id=commentsgt

ltphp if($model-gtcommentCountgt=1) gt

lth3gt

ltphp echo $model-gtcommentCount rsquocomment(s)rsquo gt

lth3gt

ltphp $this-gtrenderPartial(rsquo commentsrsquoarray(

rsquopostrsquo=gt$model

rsquocommentsrsquo=gt$model-gtcomments

)) gt

ltphp endif gt

ltdivgt

In the above we call renderPartial() to render a partial view named comments to displaythe list of comments belonging to the current post Note that in the view we use theexpression $model-gtcomments to retrieve the comments for the post This is valid becausewe have declared a comments relation in the Post class Evaluating this expression wouldtrigger an implicit JOIN database query to bring back the proper comments This featureis known as lazy relational query

The partial view comments is not very interesting It mainly goes through every commentand displays the detail of it Interested readers may refer to wwwrootyiidemosblog

protectedpost commentsphp

422 Creating Comments

To handle comment creation we first modify the actionView() method of PostController

as follows

public function actionView()

$post=$this-gtloadModel()

$comment=$this-gtnewComment($post)

$this-gtrender(rsquoviewrsquoarray(

rsquomodelrsquo=gt$post

rsquocommentrsquo=gt$comment

))

36 4 Comment Management

protected function newComment($post)

$comment=new Comment

if(isset($ POST[rsquoCommentrsquo]))

$comment-gtattributes=$ POST[rsquoCommentrsquo]

if($post-gtaddComment($comment))

if($comment-gtstatus==CommentSTATUS PENDING)

Yiiapp()-gtuser-gtsetFlash(rsquocommentSubmittedrsquorsquoThank yoursquo)

$this-gtrefresh()

return $comment

In the above we call the newComment() method before we render view In the newComment()

method we generate a Comment instance and check if the comment form is submitted If sowe try to add the comment for the post by calling $post-gtaddComment($comment) If it goesthrough we refresh the post detail page In case the comment needs to be approved we willshow a flash message to indicate this decision A flash message is usually a confirmationmessage displayed to end users If the user clicks on the refresh button of his browser themessage will disappear

We also need to modify wwwrootblogprotectedviewspostviewphp furthermore

ltdiv id=commentsgt

lth3gtLeave a Commentlth3gt

ltphp if(Yiiapp()-gtuser-gthasFlash(rsquocommentSubmittedrsquo)) gt

ltdiv class=successgt

ltphp echo Yiiapp()-gtuser-gtgetFlash(rsquocommentSubmittedrsquo) gt

ltdivgt

ltphp else gt

ltphp $this-gtrenderPartial(rsquocomment formrsquoarray(

rsquomodelrsquo=gt$comment

)) gt

ltphp endif gt

ltdivgtlt-- comments --gt

43 Managing Comments 37

In the above code we display the flash message if it is available If not we displaythe comment input form by rendering the partial view wwwrootblogprotectedviews

comment formphp

43 Managing Comments

Comment management includes updating deleting and approving comments These op-erations are implemented as actions in the CommentController class

431 Updating and Deleting Comments

The code generated by yiic for updating and deleting comments remains largely un-changed

432 Approving Comments

When comments are newly created they are in pending approval status and need to beapproved in order to be visible to guest users Approving a comment is mainly aboutchanging the status column of the comment

We create an actionApprove() method in CommentController as follows

public function actionApprove()

if(Yiiapp()-gtrequest-gtisPostRequest)

$comment=$this-gtloadModel()

$comment-gtapprove()

$this-gtredirect(array(rsquoindexrsquo))

else

throw new CHttpException(400rsquoInvalid requestrsquo)

In the above when the approve action is invoked via a POST request we call the approve()

method defined in the Comment model to change the status We then redirect the userbrowser to the page displaying the post that this comment belongs to

We also modify the actionIndex() method of Comment to show all comments We wouldlike to see comments pending approved to show up first

public function actionIndex()

38 4 Comment Management

$dataProvider=new CActiveDataProvider(rsquoCommentrsquo array(

rsquocriteriarsquo=gtarray(

rsquowithrsquo=gtrsquopostrsquo

rsquoorderrsquo=gtrsquotstatus tcreate time DESCrsquo

)

))

$this-gtrender(rsquoindexrsquoarray(

rsquodataProviderrsquo=gt$dataProvider

))

Notice that in the above code because both tbl post and tbl comment have columns statusand create time we need to disambiguate the corresponding column reference by prefixingthem with table alias names As described in the guide the alias for the primary table ina relational query is always t Therefore we are prefixing t to the status and create time

columns in the above code

Like the post index view the index view for CommentController uses [CListView] to displaythe comment list which in turn uses the partial view wwwrootblogprotectedviews

comment viewphp to display the detail of each individual comment We will not go intodetails here Interested readers may refer to the corresponding file in the blog demowwwrootyiidemosblogprotectedviewscomment viewphp

Chapter 5

Portlets

51 Customizing Page Layout

Features like rdquothe most recent commentsrdquo rdquotag cloudrdquo are better to be implemented inportlets A portlet is a pluggable user interface component that renders a fragment ofHTML code In this section we describe how to set up the portlet architecture for ourblog application

Before we go into details about developing portlets we first need to adjust our page layoutso that the portlets can be placed in a side bar

A page layout determines how to share common sections (eg header footer) amongdifferent pages and how to embed contents that are specific for different pages In Yiia page layout is represented as a view under the protectedviewslayouts folder Whenwe call the render() method in a controller the rendering result will be automaticallyembedded into this layout view before it is displayed to end users The default layoutview is named as main which means the wwwrootblogprotectedviewslayoutsmain

php file in our blog application

We could directly modify the main layout to add a side bar However doing so wouldmake some irrelevant pages such as about contact pages to have a side bar which is notalways desirable For this reason we need different layouts for different pages

We thus create two different layouts column1 and column2 The former displays the pagein a single column while the latter displays two columns in the page one of which is theside bar We will use column2 layout for the post and comment pages and column1 forthe rest of the pages Because both column1 and column2 still share the same header andfooter sections we let them to share the main layout

Based on the above description we create the column1 layout as follows

ltphp $this-gtbeginContent(rsquolayoutsmainrsquo) gt

ltdiv class=containergt

40 5 Portlets

ltdiv id=contentgt

ltphp echo $content gt

ltdivgtlt-- content --gt

ltdivgt

ltphp $this-gtendContent() gt

and the column2 layout as follows

ltphp $this-gtbeginContent(rsquolayoutsmainrsquo) gt

ltdiv class=containergt

ltdiv class=span-18gt

ltdiv id=contentgt

ltphp echo $content gt

ltdivgtlt-- content --gt

ltdivgt

ltdiv class=span-6 lastgt

ltdiv id=sidebargt

portlets to be placed here

ltdivgtlt-- sidebar --gt

ltdivgt

ltdivgt

ltphp $this-gtendContent() gt

As we can see both column1 and column2 are enclosed in a pair of beginContent andendContent calls They essentially use the specified view layoutsmain to decorate theenclosed content In all these layout views the variable $content refers to the dynamiccontent to be embedded into the layout

Besides customizing the layout view file we also need to adjust the CSS file wwwroot

blogcssmaincss so that the overall appearance would look like what we see in the blogdemo We will not go into details here

Info The skeleton application generated by the yiic tool uses the BluePrint CSSframework It provides a nice font reset and a robust grid system For more detailson how to use this framework please refer to its documentation

After creating the new layouts we need to set the layout property of controllers to makeuse of them So far we have three controllers SiteController PostController andCommentController We set SiteController to use column1 as the default layout whilethe rest two of the controllers use column2 for example

class PostController extends Controller

52 Creating User Menu Portlet 41

public $layout=rsquocolumn2rsquo

Tip If some view of the controller does not use the same layout as declaredby the layout property it may customize it in the view by a simple assignment$this-gtlayout=rsquonewlayoutrsquo

52 Creating User Menu Portlet

Based on the requirements analysis we need three different portlets the rdquouser menurdquoportlet the rdquotag cloudrdquo portlet and the rdquorecent commentsrdquo portlet We will develop theseportlets by extending the [CPortlet] widget provided by Yii

In this section we will develop our first concrete portlet - the user menu portlet whichdisplays a list of menu items that are only available to authenticated users The menucontains four items

bull Approve Comments a hyperlink that leads to a list of comments pending approval

bull Create New Post a hyperlink that leads to the post creation page

bull Manage Posts a hyperlink that leads to the post management page

bull Logout a link button that would log out the current user

521 Creating UserMenu Class

We create the UserMenu class to represent the logic part of the user menu portlet Theclass is saved in the file wwwrootblogprotectedcomponentsUserMenuphp which has thefollowing content

Yiiimport(rsquoziiwidgetsCPortletrsquo)

class UserMenu extends CPortlet

public function init()

$this-gttitle=CHtmlencode(Yiiapp()-gtuser-gtname)

parentinit()

42 5 Portlets

protected function renderContent()

$this-gtrender(rsquouserMenursquo)

The UserMenu class extends from the CPortlet class that we created previously It overridesboth the init() method and the renderContent() method of CPortlet The former setsthe portlet title to be the name of the current user the latter generates the portlet bodycontent by rendering a view named userMenu

Tip Notice that we have to explicitly include the CPortlet class by calling Yii

import() before we refer to it the first time This is because CPortlet is part of thezii project ndash the official extension library for Yii For performance considerationclasses in this project are not listed as core classes Therefore we have to import itbefore we use it the first time

522 Creating userMenu View

Next we create the userMenu view which is saved in the file wwwrootblogprotected

componentsviewsuserMenuphp

ltulgt

ltligtltphp echo CHtmllink(rsquoCreate New Postrsquoarray(rsquopostcreatersquo)) gtltligt

ltligtltphp echo CHtmllink(rsquoManage Postsrsquoarray(rsquopostadminrsquo)) gtltligt

ltligtltphp echo CHtmllink(rsquoApprove Commentsrsquoarray(rsquocommentindexrsquo))

rsquo (rsquo Commentmodel()-gtpendingCommentCount rsquo)rsquo gtltligt

ltligtltphp echo CHtmllink(rsquoLogoutrsquoarray(rsquositelogoutrsquo)) gtltligt

ltulgt

Info By default view files for a widget should be placed under the views sub-directory of the directory containing the widget class file The file name must bethe same as the view name

523 Using UserMenu Portlet

It is time for us to make use of our newly completed UserMenu portlet We modify thelayout view file wwwrootblogprotectedviewslayoutscolumn2php as follows

53 Creating Tag Cloud Portlet 43

ltdiv id=sidebargt

ltphp if(Yiiapp()-gtuser-gtisGuest) $this-gtwidget(rsquoUserMenursquo) gt

ltdivgt

In the above we call the widget() method to generate and execute an instance of theUserMenu class Because the portlet should only be displayed to authenticated users weonly call widget() when the isGuest property of the current user is false (meaning theuser is authenticated)

524 Testing UserMenu Portlet

Letrsquos test what we have so far

1 Open a browser window and enter the URL httpwwwexamplecomblogindex

php Verify that there is nothing displayed in the side bar section of the page

2 Click on the Login hyperlink and fill out the login form to login If successful verifythat the UserMenu portlet appears in the side bar and the portlet has the usernameas its title

3 Click on the rsquoLogoutrsquo hyperlink in the UserMenu portlet Verify that the logout actionis successful and the UserMenu portlet disappears

525 Summary

What we have created is a portlet that is highly reusable We can easily reuse it in adifferent project with little or no modification Moreover the design of this portlet followsclosely the philosophy that logic and presentation should be separated While we did notpoint this out in the previous sections such practice is used nearly everywhere in a typicalYii application

53 Creating Tag Cloud Portlet

Tag cloud displays a list of post tags with visual decorations hinting the popularity of eachindividual tag

531 Creating TagCloud Class

We create the TagCloud class in the file wwwrootblogprotectedcomponentsTagCloud

php The file has the following content

44 5 Portlets

Yiiimport(rsquoziiwidgetsCPortletrsquo)

class TagCloud extends CPortlet

public $title=rsquoTagsrsquo

public $maxTags=20

protected function renderContent()

$tags=Tagmodel()-gtfindTagWeights($this-gtmaxTags)

foreach($tags as $tag=gt$weight)

$link=CHtmllink(CHtmlencode($tag) array(rsquopostindexrsquorsquotagrsquo=gt$tag))

echo CHtmltag(rsquospanrsquo array(

rsquoclassrsquo=gtrsquotagrsquo

rsquostylersquo=gtfont-size$weightpt) $link)n

Unlike the UserMenu portlet the TagCloud portlet does not use a view Instead its presen-tation is done in the renderContent() method This is because the presentation does notcontain much HTML tags

We display each tag as a hyperlink to the post index page with the corresponding tagparameter The font size of each tag link is adjusted according to their relative weightamong other tags If a tag has higher frequency value than the other it will have a biggerfont size

532 Using TagCloud Portlet

Usage of the TagCloud portlet is very simple We modify the layout file wwwrootblog

protectedviewslayoutsmainphp as follows

ltdiv id=sidebargt

ltphp if(Yiiapp()-gtuser-gtisGuest) $this-gtwidget(rsquoUserMenursquo) gt

ltphp $this-gtwidget(rsquoTagCloudrsquo array(

rsquomaxTagsrsquo=gtYiiapp()-gtparams[rsquotagCloudCountrsquo]

)) gt

ltdivgt

54 Creating Recent Comments Portlet 45

54 Creating Recent Comments Portlet

In this section we create the last portlet that displays a list of comments recently pub-lished

541 Creating RecentComments Class

We create the RecentComments class in the file wwwrootblogprotectedcomponentsRecentCommentsphp The file has the following content

Yiiimport(rsquoziiwidgetsCPortletrsquo)

class RecentComments extends CPortlet

public $title=rsquoRecent Commentsrsquo

public $maxComments=10

public function getRecentComments()

return Commentmodel()-gtfindRecentComments($this-gtmaxComments)

protected function renderContent()

$this-gtrender(rsquorecentCommentsrsquo)

In the above we invoke the findRecentComments method which is defined in the Comment

class as follows

class Comment extends CActiveRecord

public function findRecentComments($limit=10)

return $this-gtwith(rsquopostrsquo)-gtfindAll(array(

rsquoconditionrsquo=gtrsquotstatus=rsquoselfSTATUS APPROVED

rsquoorderrsquo=gtrsquotcreate time DESCrsquo

rsquolimitrsquo=gt$limit

))

46 5 Portlets

542 Creating recentComments View

The recentComments view is saved in the file wwwrootblogprotectedcomponentsviews

recentCommentsphp It simply displays every comment returned by the RecentComments

getRecentComments() method

543 Using RecentComments Portlet

We modify the layout file wwwrootblogprotectedviewslayoutsmainphp to embedthis last portlet

ltdiv id=sidebargt

ltphp if(Yiiapp()-gtuser-gtisGuest) $this-gtwidget(rsquoUserMenursquo) gt

ltphp $this-gtwidget(rsquoTagCloudrsquo array(

rsquomaxTagsrsquo=gtYiiapp()-gtparams[rsquotagCloudCountrsquo]

)) gt

ltphp $this-gtwidget(rsquoRecentCommentsrsquo array(

rsquomaxCommentsrsquo=gtYiiapp()-gtparams[rsquorecentCommentCountrsquo]

)) gt

ltdivgt

Chapter 6

Final Work

61 Beautifying URLs

The URLs linking various pages of our blog application currently look ugly For examplethe URL for the page showing a post looks like the following

indexphpr=postshowampid=1

In this section we describe how to beautify these URLs and make them SEO-friendlyOur goal is to be able to use the following URLs in the application

1 indexphppostsyii leads to the page showing a list of posts with tag yii

2 indexphppost2A+Test+Post leads to the page showing the detail of the postwith ID 2 whose title is A Test Post

3 indexphppostupdate1 leads to the page that allows updating the post with ID1

Note that in the second URL format we include the post title in the URL This is mainlyto make the URL SEO friendly It is said that search engines may also respect the wordsfound in a URL when it is being indexed

To achieve our goal we modify the application configuration as follows

return array(

rsquocomponentsrsquo=gtarray(

rsquourlManagerrsquo=gtarray(

rsquourlFormatrsquo=gtrsquopathrsquo

rsquorulesrsquo=gtarray(

48 6 Final Work

rsquopostltidd+gtlttitlegtrsquo=gtrsquopostviewrsquorsquopostslttaggtrsquo=gtrsquopostindexrsquo

)

)

)

)

In the above we configure the urlManager component by setting its urlFormat propertyto be path and adding a set of rules

The rules are used by urlManager to parse and create the URLs in the desired formatFor example the first rule says that if a URL indexphppostsyii is requested theurlManager component should be responsible to dispatch the request to the route post

index and generate a tag GET parameter with the value yii On the other hand whencreating a URL with the route postindex and parameter tag the urlManager componentwill also use this rule to generate the desired URL indexphppostsyii For this reasonwe say that urlManager is a two-way URL manager

The urlManager component can further beautify our URLs such as hiding indexphp inthe URLs appending suffix like html to the URLs We can obtain these features easilyby configuring various properties of urlManager in the application configuration For moredetails please refer to the Guide

62 Logging Errors

A production Web application often needs sophisticated logging for various events In ourblog application we would like to log the errors occurring when it is being used Sucherrors could be programming mistakes or usersrsquo misuse of the system Logging these errorswill help us to improve the blog application

We enable the error logging by modifying the application configuration as follows

return array(

rsquopreloadrsquo=gtarray(rsquologrsquo)

rsquocomponentsrsquo=gtarray(

rsquologrsquo=gtarray(

rsquoclassrsquo=gtrsquoCLogRouterrsquo

rsquoroutesrsquo=gtarray(

array(

rsquoclassrsquo=gtrsquoCFileLogRoutersquo

rsquolevelsrsquo=gtrsquoerror warningrsquo

63 Final Tune-up and Deployment 49

)

)

)

)

)

With the above configuration if an error or warning occurs detailed information will belogged and saved in a file located under the directory wwwrootblogprotectedruntime

The log component offers more advanced features such as sending log messages to a listof email addresses displaying log messages in JavaScript console window etc For moredetails please refer to the Guide

63 Final Tune-up and Deployment

We are close to finish our blog application Before deployment we would like to do sometune-ups

631 Changing Home Page

We change to use the post list page as the home page We modify the application config-uration as follows

return array(

rsquodefaultControllerrsquo=gtrsquopostrsquo

)

Tip Because PostController already declares list to be its default action whenwe access the home page of the application we will see the result generated by thelist action of the post controller

632 Enabling Schema Caching

Because ActiveRecord relies on the metadata about tables to determine the column infor-mation it takes time to read the metadata and analyze it This may not be a problemduring development stage but for an application running in production mode it is a totalwaste of time if the database schema does not change Therefore we should enable theschema caching by modifying the application configuration as follows

50 6 Final Work

return array(

rsquocomponentsrsquo=gtarray(

rsquocachersquo=gtarray(

rsquoclassrsquo=gtrsquoCDbCachersquo

)

rsquodbrsquo=gtarray(

rsquoclassrsquo=gtrsquosystemdbCDbConnectionrsquo

rsquoconnectionStringrsquo=gtrsquosqlitewwwrootblogprotecteddatablogdbrsquo

rsquoschemaCachingDurationrsquo=gt3600

)

)

)

In the above we first add a cache component which uses a default SQLite database as thecaching storage If our server is equipped with other caching extensions such as APCwe could change to use them as well We also modify the db component by setting itsschemaCachingDuration property to be 3600 which means the parsed database schemadata can remain valid in cache for 3600 seconds

633 Disabling Debugging Mode

We modify the entry script file wwwrootblogindexphp by removing the line definingthe constant YII DEBUG This constant is useful during development stage because it allowsYii to display more debugging information when an error occurs However when theapplication is running in production mode displaying debugging information is not agood idea because it may contain sensitive information such as where the script file islocated and the content in the file etc

634 Deploying the Application

The final deployment process manly involves copying the directory wwwrootblog to thetarget directory The following checklist shows every needed step

1 Install Yii in the target place if it is not available

2 Copy the entire directory wwwrootblog to the target place

3 Edit the entry script file indexphp by pointing the $yii variable to the new Yiibootstrap file

4 Edit the file protectedyiicphp by setting the $yiic variable to be the new Yiiyiicphp file

64 Future Enhancements 51

5 Change the permission of the directories assets and protectedruntime so that theyare writable by the Web server process

64 Future Enhancements

641 Using a Theme

Without writing any code our blog application is already themeable To use a themewe mainly need to develop the theme by writing customized view files in the theme Forexample to use a theme named classic that uses a different page layout we would createa layout view file wwwrootblogthemesclassicviewslayoutsmainphp We also needto change the application configuration to indicate our choice of the classic theme

return array(

rsquothemersquo=gtrsquoclassicrsquo

)

642 Internationalization

We may also internationalize our blog application so that its pages can be displayed indifferent languages This mainly involves efforts in two aspects

First we may create view files in different languages For example for the list pageof PostController we can create a view file wwwrootblogprotectedviewspostzh cn

listphp When the application is configured to use simplified Chinese (the language codeis zh cn) Yii will automatically use this new view file instead of the original one

Second we may create message translations for those messages generated by code Themessage translations should be saved as files under the directory wwwrootblogprotected

messages We also need to modify the code where we use text strings by enclosing themin the method call Yiit()

For more details about internationalization please refer to the Guide

643 Improving Performance with Cache

While the Yii framework itself is very efficient it is not necessarily true that an applicationwritten in Yii efficient There are several places in our blog application that we can improvethe performance For example the tag clould portlet could be one of the performancebottlenecks because it involves complex database query and PHP logic

52 6 Final Work

We can make use of the sophisticated caching feature provided by Yii to improve theperformance One of the most useful components in Yii is COutputCache which caches afragment of page display so that the underlying code generating the fragment does not needto be executed for every request For example in the layout file wwwrootblogprotected

viewslayoutsmainphp we can enclose the tag cloud portlet with COutputCache

ltphp if($this-gtbeginCache(rsquotagCloudrsquo array(rsquodurationrsquo=gt3600))) gt

ltphp $this-gtwidget(rsquoTagCloudrsquo array(

rsquomaxTagsrsquo=gtYiiapp()-gtparams[rsquotagCloudCountrsquo]

)) gt

ltphp $this-gtendCache() gt

With the above code the tag cloud display will be served from cache instead of beinggenerated on-the-fly for every request The cached content will remain valid in cache for3600 seconds

644 Adding New Features

Our blog application only has very basic functionalities To become a complete blogsystem more features are needed for example calendar portlet email notifications postcategorization archived post portlet and so on We will leave the implementation of thesefeatures to interested readers

  • Contents
  • License
  • Getting Started
    • Building a Blog System using Yii
    • Testdriving with Yii
      • Installing Yii
      • Creating Skeleton Application
      • Application Workflow
        • Requirements Analysis
        • Overall Design
          • Initial Prototyping
            • Setting Up Database
              • Creating Database
              • Establishing Database Connection
                • Scaffolding
                • Authenticating User
                • Summary
                  • Post Management
                    • Customizing Post Model
                      • Customizing rules() Method
                      • Customizing relations() Method
                      • Representing Status in Text
                        • Creating and Updating Posts
                          • Customizing Access Control
                          • Customizing create and update Operations
                            • Displaying Posts
                              • Customizing view Operation
                              • Customizing index Operation
                                • Managing Posts
                                  • Listing Posts in Tabular View
                                  • Deleting Posts
                                      • Comment Management
                                        • Customizing Comment Model
                                          • Customizing rules() Method
                                          • Customizing attributeLabels() Method
                                          • Customizing Saving Process
                                            • Creating and Displaying Comments
                                              • Displaying Comments
                                              • Creating Comments
                                                • Managing Comments
                                                  • Updating and Deleting Comments
                                                  • Approving Comments
                                                      • Portlets
                                                        • Customizing Page Layout
                                                        • Creating User Menu Portlet
                                                          • Creating UserMenu Class
                                                          • Creating userMenu View
                                                          • Using UserMenu Portlet
                                                          • Testing UserMenu Portlet
                                                          • Summary
                                                            • Creating Tag Cloud Portlet
                                                              • Creating TagCloud Class
                                                              • Using TagCloud Portlet
                                                                • Creating Recent Comments Portlet
                                                                  • Creating RecentComments Class
                                                                  • Creating recentComments View
                                                                  • Using RecentComments Portlet
                                                                      • Final Work
                                                                        • Beautifying URLs
                                                                        • Logging Errors
                                                                        • Final Tune-up and Deployment
                                                                          • Changing Home Page
                                                                          • Enabling Schema Caching
                                                                          • Disabling Debugging Mode
                                                                          • Deploying the Application
                                                                            • Future Enhancements
                                                                              • Using a Theme
                                                                              • Internationalization
                                                                              • Improving Performance with Cache
                                                                              • Adding New Features
Page 27: Yii Blog 1.1.0 Demo Tutor

20 3 Post Management

array(rsquotagsrsquo rsquonormalizeTagsrsquo)

)

In the above we specify that the title content and status attributes are required thelength of title should not exceed 128 the status attribute value should be 1 (draft) 2(published) or 3 (archived) and the tags attribute should only contain word charactersand commas In addition we use normalizeTags to normalize the user-entered tags so thatthe tags are unique and properly separated with commas

The validators such as required length in and match are all built-in validators providedby Yii The normalizeTags validator is a method-based validator that we need to definein the Post class For more information about how to specify validation rules please referto the Guide

public function normalizeTags($attribute$params)

$this-gttags=Tagarray2string(array unique(Tagstring2array($this-gttags)))

The rules declared in this method are executed one by one when we call the validate() orsave() method of the model instance

Note It is very important to remember that attributes appearing in rules() mustbe those to be entered by end users Other attributes such as id and create time

in the Post model which are set by our code or database should not be in rules()For more details please refer to Securing Attribute Assignments

After making these changes we can visit the post creation page again to verify that thenew validation rules are taking effect

312 Customizing relations() Method

Lastly we customize the relations() method to specify the related objects of a post Bydeclaring these related objects in relations() we can exploit the powerful RelationalActiveRecord (RAR) feature to access the related object information of a post such asits author and comments without the need to write complex SQL JOIN statements

We customize the relations() method as follows

31 Customizing Post Model 21

public function relations()

return array(

rsquoauthorrsquo =gt array(selfBELONGS TO rsquoUserrsquo rsquoauthor idrsquo)

rsquocommentsrsquo =gt array(selfHAS MANY rsquoCommentrsquo rsquopost idrsquo

rsquoconditionrsquo=gtrsquocommentsstatus=rsquoCommentSTATUS APPROVED

rsquoorderrsquo=gtrsquocommentscreate time DESCrsquo)

rsquocommentCountrsquo =gt array(selfSTAT rsquoCommentrsquo rsquopost idrsquo

rsquoconditionrsquo=gtrsquostatus=rsquoCommentSTATUS APPROVED)

)

The above relations state that

bull A post belongs to an author whose class is User and the relationship is establishedbased on the author id attribute value of the post

bull A post has many comments whose class is Comment and the relationship is establishedbased on the post id attribute value of the comments These comments should besorted according to their creation time and the comments must be approved

bull The commentCount relation is a bit special as it returns back an aggregation resultwhich is about how many comments the post has

With the above relation declaration we can easily access the author and comments of apost like the following

$author=$post-gtauthor

echo $author-gtusername

$comments=$post-gtcomments

foreach($comments as $comment)

echo $comment-gtcontent

For more details about how to declare and use relations please refer to the Guide

313 Representing Status in Text

Because the status of a post is stored as an integer in the database we need to provide atextual representation so that it is more intuitive when being displayed to end users In alarge system the similar requirement is very common

22 3 Post Management

As a generic solution we use the tbl lookup table to store the mapping between integervalues and textual representations that are needed by other data objects We modify theLookup model class as follows to more easily access the textual data in the table

class Lookup extends CActiveRecord

private static $ items=array()

public static function items($type)

if(isset(self$ items[$type]))

selfloadItems($type)

return self$ items[$type]

public static function item($type$code)

if(isset(self$ items[$type]))

selfloadItems($type)

return isset(self$ items[$type][$code]) self$ items[$type][$code] false

private static function loadItems($type)

self$ items[$type]=array()

$models=selfmodel()-gtfindAll(array(

rsquoconditionrsquo=gtrsquotype=typersquo

rsquoparamsrsquo=gtarray(rsquotypersquo=gt$type)

rsquoorderrsquo=gtrsquopositionrsquo

))

foreach($models as $model)

self$ items[$type][$model-gtcode]=$model-gtname

Our new code mainly provides two static methods Lookupitems() and Lookupitem()The former returns a list of strings belonging to the specified data type while the latterreturns a particular string for the given data type and data value

Our blog database is pre-populated with two lookup types PostStatus and CommentStatusThe former refers to the possible post statuses while the latter the comment statuses

In order to make our code easier to read we also declare a set of constants to representthe status integer values We should use these constants through our code when referringto the corresponding status values

32 Creating and Updating Posts 23

class Post extends CActiveRecord

const STATUS DRAFT=0

const STATUS PUBLISHED=1

const STATUS ARCHIVED=2

Therefore we can call Lookupitems(rsquoPostStatusrsquo) to get the list of possible post statuses(text strings indexed by the corresponding integer values) and call Lookupitem(rsquoPostStatusrsquoPostSTATUS PUBLISHED) to get the string representation of the published status

32 Creating and Updating Posts

With the Post model ready we need to fine-tune the actions and views for the controllerPostController In this section we first customize the access control of CRUD operationswe then modify the code implementing the create and update operations

321 Customizing Access Control

The first thing we want to do is to customize the access control because the code generatedby yiic does not fit our needs

We modify the accessRules() method in the file wwwrootblogprotectedcontrollers

PostControllerphp as follows

public function accessRules()

return array(

array(rsquoallowrsquo allow all users to perform rsquolistrsquo and rsquoshowrsquo actions

rsquoactionsrsquo=gtarray(rsquoindexrsquo rsquoviewrsquo)

rsquousersrsquo=gtarray(rsquorsquo)

)

array(rsquoallowrsquo allow authenticated users to perform any action

rsquousersrsquo=gtarray(rsquorsquo)

)

array(rsquodenyrsquo deny all users

rsquousersrsquo=gtarray(rsquorsquo)

)

)

The above rules state that all users can access the index and view actions and authenti-cated users can access any actions including the admin action The user should be denied

24 3 Post Management

access in any other scenario Note that these rules are evaluated in the order they arelisted here The first rule matching the current context makes the access decision Forexample if the current user is the system owner who tries to visit the post creation pagethe second rule will match and it will give the access to the user

322 Customizing create and update Operations

The create and update operations are very similar They both need to display an HTMLform to collect user inputs validate them and save them into database The main differ-ence is that the update operation will pre-populate the form with the existing post datafound in the database For this reason the yiic tool generates a partial view wwwroot

blogprotectedviewspost formphp that is embedded in both the create and update

views to render the needed HTML form

We first change the formphp file so that the HTML form only collects the inputs wewant title content tags and status We use plain text fields to collect inputs for thefirst three attributes and a dropdown list to collect input for status The dropdown listoptions are the text displays of the possible post statuses

ltphp echo CHtmlactiveDropDownList($postrsquostatusrsquoLookupitems(rsquoPostStatusrsquo)) gt

In the above we call Lookupitems(rsquoPostStatusrsquo) to bring back the list of post statuses

We then modify the Post class so that it can automatically set some attributes (eg create

time author id) before a post is saved to the database We override the beforeSave()

method as follows

protected function beforeSave()

if(parentbeforeSave())

if($this-gtisNewRecord)

$this-gtcreate time=$this-gtupdate time=time()

$this-gtauthor id=Yiiapp()-gtuser-gtid

else

$this-gtupdate time=time()

return true

else

return false

33 Displaying Posts 25

When we save a post we want to update the tbl tag table to reflect the change of tagfrequencies We can do this work in the afterSave() method which is automaticallyinvoked by Yii after a post is successfully saved into the database

protected function afterSave()

parentafterSave()

Tagmodel()-gtupdateFrequency($this-gt oldTags $this-gttags)

private $ oldTags

protected function afterFind()

parentafterFind()

$this-gt oldTags=$this-gttags

In the implementation because we want to detect if the user changes the tags in case he isupdating an existing post we need to know what the old tags are For this reason we alsowrite the afterFind() method to keep the old tags in the variable oldTags The methodafterFind() is invoked automatically by Yii when an AR record is populated with thedata from database

We are not going to give details of the TagupdateFrequency() method here Interestedreaders may refer to the file wwwrootyiidemosblogprotectedmodelsTagphp

33 Displaying Posts

In our blog application a post may be displayed among a list of posts or by itself Theformer is implemented as the index operation while the latter the view operation In thissection we customize both operations to fulfill our initial requirements

331 Customizing view Operation

The view operation is implemented by the actionView() method in PostController Itsdisplay is generated by the view view with the view file wwwrootblogprotectedviews

postviewphp

Below is the relevant code implementing the view operation in PostController

public function actionView()

26 3 Post Management

$this-gtrender(rsquoviewrsquoarray(

$post=$this-gtloadModel()

$this-gtrender(rsquoviewrsquoarray(

rsquomodelrsquo=gt$post

))

))

private $ model

public function loadModel()

if($this-gt model===null)

if(isset($ GET[rsquoidrsquo]))

if(Yiiapp()-gtuser-gtisGuest)

$condition=rsquostatus=rsquoPostSTATUS PUBLISHED

rsquo OR status=rsquoPOSTSTATUS ARCHIVED

else

$condition=rsquorsquo

$this-gt model=Postmodel()-gtfindbyPk($ GET[rsquoidrsquo] $condition)

if($this-gt model===null)

throw new CHttpException(404rsquoThe requested page does not existrsquo)

return $this-gt model

Our change mainly lies in the loadModel() method In this method we query the Post

table according to the id GET parameter If the post is not found or if it is not publishedor archived (when the user is a guest) we will throw a 404 HTTP error Otherwise thepost object is returned to actionView() which in turn passes the post object to the viewscript for further display

Tip Yii captures HTTP exceptions (instances of CHttpException) and displaysthem in either predefined templates or customized error views The skeleton appli-cation generated by yiic already contains a customized error view in wwwroot

blogprotectedviewssiteerrorphp We can modify this file if we want tofurther customize the error display

The change in the view script is mainly about ajdusting the formatting and styles of thepost display We will not go into details here Interested readers may refer to wwwroot

blogprotectedviewspostviewphp

33 Displaying Posts 27

332 Customizing index Operation

Like the view operation we customize the index operation in two places the actionIndex()

method in PostController and the view file wwwrootblogprotectedviewspostindex

php We mainly need to add the support for displaying a list of posts that are associatedwith a specified tag

Below is the modified actionIndex() method in PostController

public function actionIndex()

$criteria=new CDbCriteria(array(

rsquoconditionrsquo=gtrsquostatus=rsquoPostSTATUS PUBLISHED

rsquoorderrsquo=gtrsquoupdate time DESCrsquo

rsquowithrsquo=gtrsquocommentCountrsquo

))

if(isset($ GET[rsquotagrsquo]))

$criteria-gtaddSearchCondition(rsquotagsrsquo$ GET[rsquotagrsquo])

$dataProvider=new CActiveDataProvider(rsquoPostrsquo array(

rsquopaginationrsquo=gtarray(

rsquopageSizersquo=gtYiiapp()-gtparams[rsquopostsPerPagersquo]

)

rsquocriteriarsquo=gt$criteria

))

$this-gtrender(rsquoindexrsquoarray(

rsquodataProviderrsquo=gt$dataProvider

))

In the above we first create a query criteria for retrieving post list The criteria statesthat only published posts should be returned and they should be sorted according to theirupdate time in descending order Because when displaying a post in the list we want toshow how many comments the post has received in the criteria we also specify to bringback commentCount which if you remember is a relation declared in Postrelations()

In case when a user wants to see posts with a specific tag we would add a search conditionto the criteria to look for the specified tag

Using the query criteria we create a data provider which mainly serves for three purposesFirst it does pagination of the data when too many results may be returned Herewe customize the pagination by setting the page size to be an application parameterpostsPerPage Second it does sorting according to the user request And finally it feedsthe paginated and sorted data to widgets or view code for presentation

28 3 Post Management

After we finish with actionIndex() we modify the index view as follows Our change ismainly about adding the h1 header when the user specifies to display posts with a tag

ltphp if(empty($ GET[rsquotagrsquo])) gt

lth1gtPosts Tagged with ltigtltphp echo CHtmlencode($ GET[rsquotagrsquo]) gtltigtlth1gt

ltphp endif gt

ltphp $this-gtwidget(rsquoziiwidgetsCListViewrsquo array(

rsquodataProviderrsquo=gt$dataProvider

rsquoitemViewrsquo=gtrsquo viewrsquo

rsquotemplatersquo=gtitemsnpager)) gt

Note that in the above we use [CListView] to display the post list This widget requires apartial view to display the detail of each individual post Here we specify the partial viewto be view which means the file wwwrootblogprotectedviewspost viewphp In thisview script we can acccess the post instance being displayed via a local variable named$data

34 Managing Posts

Managing posts mainly refers to listing posts in an administrative view that allows us tosee posts with all statuses updating them and deleting them They are accomplished bythe admin operation and the delete operation respectively The code generated by yiic

does not need much modification Below we mainly explain how these two operations areimplemented

341 Listing Posts in Tabular View

The admin operation shows posts with all statuses in a tabular view The view supportssorting and pagination The following is the actionAdmin() method in PostController

public function actionAdmin()

$dataProvider=new CActiveDataProvider(rsquoPostrsquo array(

rsquosortrsquo=gtarray(

rsquodefaultOrderrsquo=gtrsquostatus update time DESCrsquo

)

))

$this-gtrender(rsquoadminrsquoarray(

rsquodataProviderrsquo=gt$dataProvider

))

34 Managing Posts 29

The above code is very similar to that in actionIndex() The main difference is in thequery criteria Here we do not specify any particular query criteria since we want to seeall posts We do customize the sort property of the data provider so that by default theposts are shown in the order of their statuses and update time

Below is the code for the admin view

lth1gtManage Postslth1gt

ltphp $this-gtwidget(rsquoziiwidgetsgridCGridViewrsquo array(

rsquodataProviderrsquo=gt$dataProvider

rsquocolumnsrsquo=gtarray(

array(

rsquonamersquo=gtrsquotitlersquo

rsquotypersquo=gtrsquorawrsquo

rsquovaluersquo=gtrsquoCHtmllink(CHtmlencode($data-gttitle) $data-gturl)rsquo

)

array(

rsquonamersquo=gtrsquostatusrsquo

rsquovaluersquo=gtrsquoLookupitem(PostStatus$data-gtstatus)rsquo

)

rsquocreate timedatetimersquo

rsquoupdate timedatetimersquo

array(

rsquoclassrsquo=gtrsquoCButtonColumnrsquo

)

)

)) gt

We use [CGridView] to display the posts It allows us to sort by a column and paginatethrough the posts if there are too many to be displayed in a single page Our change ismainly about how to display each column For example for the title column we specifythat it should be displayed as a hyperlink that points to the detailed view of the post

Tip When displaying text we call CHtmlencode() to encode HTML entities init This prevents from cross-site scripting attack

342 Deleting Posts

In the admin data grid there is a delete button in each row Clicking on the button shoulddelete the corresponding post Internally this triggers the delete action implemented asfollows

30 3 Post Management

public function actionDelete()

if(Yiiapp()-gtrequest-gtisPostRequest)

we only allow deletion via POST request

$this-gtloadModel()-gtdelete()

if(isset($ POST[rsquoajaxrsquo]))

$this-gtredirect(array(rsquoindexrsquo))

else

throw new CHttpException(400rsquoInvalid request Please do not repeat this request againrsquo)

The above code is the one generated by the yiic tool without any change We wouldlike to explain a little bit more about the checking on $ POST[rsquoajaxrsquo] The [CGridView]widget has a very nice feature that its sorting pagination and deletion operations are alldone in AJAX mode by default That means the whole page does not get reloaded if anyof the above operations is performed However it is also possible that the widget runs innon-AJAX mode (by setting its ajaxUpdate property to be false or disabling JavaScript onthe client side) It is necessary for the delete action to differentiate these two scenarios ifthe delete request is made via AJAX we should not redirect the user browser otherwisewe should

Deleting a post should also cause the deletion of all comments for that post In additionwe should also update the tbl tag table regarding the tags for the deleted post Both ofthese tasks can be achieved by writing an afterDelete method in the Post model class asfollows

protected function afterDelete()

parentafterDelete()

Commentmodel()-gtdeleteAll(rsquopost id=rsquo$this-gtid)

Tagmodel()-gtupdateFrequency($this-gttags rsquorsquo)

The above code is very straightforward it first deletes all those comments whose post id

is the same as the ID of the deleted post it then updates the tbl tag table for the tags

of the deleted post

34 Managing Posts 31

Tip We have to explicitly delete all comments for the deleted post here becauseSQLite does not really support foreign key constraints In a DBMS that supportsthis constraint (such as MySQL PostgreSQL) the foreign key constraint can be setup such that the DBMS automatically deletes the related comments if the post isdeleted In that case we no longer this explicit deletion call in our code

32 3 Post Management

Chapter 4

Comment Management

41 Customizing Comment Model

For the Comment model we mainly need to customize the rules() and attributeLabels()

methods The attributeLabels() method returns a mapping between attribute namesand attribute labels We do not need to touch relations() since the code generated bythe yiic tool is good enough

411 Customizing rules() Method

We first customize the validation rules generated by the yiic tool The following rules areused for comments

public function rules()

return array(

array(rsquocontent author emailrsquo rsquorequiredrsquo)

array(rsquoauthor email urlrsquo rsquolengthrsquo rsquomaxrsquo=gt128)

array(rsquoemailrsquorsquoemailrsquo)

array(rsquourlrsquorsquourlrsquo)

)

In the above we specify that the author email and content attributes are required thelength of author email and url cannot exceed 128 the email attribute must be a validemail address and the url attribute must be a valid URL

412 Customizing attributeLabels() Method

We then customize the attributeLabels() method to declare the label display for eachmodel attribute This method returns an array consisting of name-label pairs When wecall CHtmlactiveLabel() to display an attribute label

34 4 Comment Management

public function attributeLabels()

return array(

rsquoidrsquo =gt rsquoIdrsquo

rsquocontentrsquo =gt rsquoCommentrsquo

rsquostatusrsquo =gt rsquoStatusrsquo

rsquocreate timersquo =gt rsquoCreate Timersquo

rsquoauthorrsquo =gt rsquoNamersquo

rsquoemailrsquo =gt rsquoEmailrsquo

rsquourlrsquo =gt rsquoWebsitersquo

rsquopost idrsquo =gt rsquoPostrsquo

)

Tip If the label for an attribute is not declared in attributeLabels() an algo-rithm will be used to generate an appropriate label For example a label VerifyCode will be generated for attributes verify code or verifyCode

413 Customizing Saving Process

Because we want to record the creation time of a comment we override the beforeSave()

method of Comment like we do for the Post model

protected function beforeSave()

if(parentbeforeSave())

if($this-gtisNewRecord)

$this-gtcreate time=time()

return true

else

return false

42 Creating and Displaying Comments

In this section we implement the comment display and creation features

421 Displaying Comments

Instead of displaying and creating comments on individual pages we use the post detailpage (generated by the view action of PostController) Below the post content display

42 Creating and Displaying Comments 35

we display first a list of comments belonging to that post and then a comment creationform

In order to display comments on the post detail page we modify the view script wwwrootblogprotectedviewspostviewphp as follows

ltdiv id=commentsgt

ltphp if($model-gtcommentCountgt=1) gt

lth3gt

ltphp echo $model-gtcommentCount rsquocomment(s)rsquo gt

lth3gt

ltphp $this-gtrenderPartial(rsquo commentsrsquoarray(

rsquopostrsquo=gt$model

rsquocommentsrsquo=gt$model-gtcomments

)) gt

ltphp endif gt

ltdivgt

In the above we call renderPartial() to render a partial view named comments to displaythe list of comments belonging to the current post Note that in the view we use theexpression $model-gtcomments to retrieve the comments for the post This is valid becausewe have declared a comments relation in the Post class Evaluating this expression wouldtrigger an implicit JOIN database query to bring back the proper comments This featureis known as lazy relational query

The partial view comments is not very interesting It mainly goes through every commentand displays the detail of it Interested readers may refer to wwwrootyiidemosblog

protectedpost commentsphp

422 Creating Comments

To handle comment creation we first modify the actionView() method of PostController

as follows

public function actionView()

$post=$this-gtloadModel()

$comment=$this-gtnewComment($post)

$this-gtrender(rsquoviewrsquoarray(

rsquomodelrsquo=gt$post

rsquocommentrsquo=gt$comment

))

36 4 Comment Management

protected function newComment($post)

$comment=new Comment

if(isset($ POST[rsquoCommentrsquo]))

$comment-gtattributes=$ POST[rsquoCommentrsquo]

if($post-gtaddComment($comment))

if($comment-gtstatus==CommentSTATUS PENDING)

Yiiapp()-gtuser-gtsetFlash(rsquocommentSubmittedrsquorsquoThank yoursquo)

$this-gtrefresh()

return $comment

In the above we call the newComment() method before we render view In the newComment()

method we generate a Comment instance and check if the comment form is submitted If sowe try to add the comment for the post by calling $post-gtaddComment($comment) If it goesthrough we refresh the post detail page In case the comment needs to be approved we willshow a flash message to indicate this decision A flash message is usually a confirmationmessage displayed to end users If the user clicks on the refresh button of his browser themessage will disappear

We also need to modify wwwrootblogprotectedviewspostviewphp furthermore

ltdiv id=commentsgt

lth3gtLeave a Commentlth3gt

ltphp if(Yiiapp()-gtuser-gthasFlash(rsquocommentSubmittedrsquo)) gt

ltdiv class=successgt

ltphp echo Yiiapp()-gtuser-gtgetFlash(rsquocommentSubmittedrsquo) gt

ltdivgt

ltphp else gt

ltphp $this-gtrenderPartial(rsquocomment formrsquoarray(

rsquomodelrsquo=gt$comment

)) gt

ltphp endif gt

ltdivgtlt-- comments --gt

43 Managing Comments 37

In the above code we display the flash message if it is available If not we displaythe comment input form by rendering the partial view wwwrootblogprotectedviews

comment formphp

43 Managing Comments

Comment management includes updating deleting and approving comments These op-erations are implemented as actions in the CommentController class

431 Updating and Deleting Comments

The code generated by yiic for updating and deleting comments remains largely un-changed

432 Approving Comments

When comments are newly created they are in pending approval status and need to beapproved in order to be visible to guest users Approving a comment is mainly aboutchanging the status column of the comment

We create an actionApprove() method in CommentController as follows

public function actionApprove()

if(Yiiapp()-gtrequest-gtisPostRequest)

$comment=$this-gtloadModel()

$comment-gtapprove()

$this-gtredirect(array(rsquoindexrsquo))

else

throw new CHttpException(400rsquoInvalid requestrsquo)

In the above when the approve action is invoked via a POST request we call the approve()

method defined in the Comment model to change the status We then redirect the userbrowser to the page displaying the post that this comment belongs to

We also modify the actionIndex() method of Comment to show all comments We wouldlike to see comments pending approved to show up first

public function actionIndex()

38 4 Comment Management

$dataProvider=new CActiveDataProvider(rsquoCommentrsquo array(

rsquocriteriarsquo=gtarray(

rsquowithrsquo=gtrsquopostrsquo

rsquoorderrsquo=gtrsquotstatus tcreate time DESCrsquo

)

))

$this-gtrender(rsquoindexrsquoarray(

rsquodataProviderrsquo=gt$dataProvider

))

Notice that in the above code because both tbl post and tbl comment have columns statusand create time we need to disambiguate the corresponding column reference by prefixingthem with table alias names As described in the guide the alias for the primary table ina relational query is always t Therefore we are prefixing t to the status and create time

columns in the above code

Like the post index view the index view for CommentController uses [CListView] to displaythe comment list which in turn uses the partial view wwwrootblogprotectedviews

comment viewphp to display the detail of each individual comment We will not go intodetails here Interested readers may refer to the corresponding file in the blog demowwwrootyiidemosblogprotectedviewscomment viewphp

Chapter 5

Portlets

51 Customizing Page Layout

Features like rdquothe most recent commentsrdquo rdquotag cloudrdquo are better to be implemented inportlets A portlet is a pluggable user interface component that renders a fragment ofHTML code In this section we describe how to set up the portlet architecture for ourblog application

Before we go into details about developing portlets we first need to adjust our page layoutso that the portlets can be placed in a side bar

A page layout determines how to share common sections (eg header footer) amongdifferent pages and how to embed contents that are specific for different pages In Yiia page layout is represented as a view under the protectedviewslayouts folder Whenwe call the render() method in a controller the rendering result will be automaticallyembedded into this layout view before it is displayed to end users The default layoutview is named as main which means the wwwrootblogprotectedviewslayoutsmain

php file in our blog application

We could directly modify the main layout to add a side bar However doing so wouldmake some irrelevant pages such as about contact pages to have a side bar which is notalways desirable For this reason we need different layouts for different pages

We thus create two different layouts column1 and column2 The former displays the pagein a single column while the latter displays two columns in the page one of which is theside bar We will use column2 layout for the post and comment pages and column1 forthe rest of the pages Because both column1 and column2 still share the same header andfooter sections we let them to share the main layout

Based on the above description we create the column1 layout as follows

ltphp $this-gtbeginContent(rsquolayoutsmainrsquo) gt

ltdiv class=containergt

40 5 Portlets

ltdiv id=contentgt

ltphp echo $content gt

ltdivgtlt-- content --gt

ltdivgt

ltphp $this-gtendContent() gt

and the column2 layout as follows

ltphp $this-gtbeginContent(rsquolayoutsmainrsquo) gt

ltdiv class=containergt

ltdiv class=span-18gt

ltdiv id=contentgt

ltphp echo $content gt

ltdivgtlt-- content --gt

ltdivgt

ltdiv class=span-6 lastgt

ltdiv id=sidebargt

portlets to be placed here

ltdivgtlt-- sidebar --gt

ltdivgt

ltdivgt

ltphp $this-gtendContent() gt

As we can see both column1 and column2 are enclosed in a pair of beginContent andendContent calls They essentially use the specified view layoutsmain to decorate theenclosed content In all these layout views the variable $content refers to the dynamiccontent to be embedded into the layout

Besides customizing the layout view file we also need to adjust the CSS file wwwroot

blogcssmaincss so that the overall appearance would look like what we see in the blogdemo We will not go into details here

Info The skeleton application generated by the yiic tool uses the BluePrint CSSframework It provides a nice font reset and a robust grid system For more detailson how to use this framework please refer to its documentation

After creating the new layouts we need to set the layout property of controllers to makeuse of them So far we have three controllers SiteController PostController andCommentController We set SiteController to use column1 as the default layout whilethe rest two of the controllers use column2 for example

class PostController extends Controller

52 Creating User Menu Portlet 41

public $layout=rsquocolumn2rsquo

Tip If some view of the controller does not use the same layout as declaredby the layout property it may customize it in the view by a simple assignment$this-gtlayout=rsquonewlayoutrsquo

52 Creating User Menu Portlet

Based on the requirements analysis we need three different portlets the rdquouser menurdquoportlet the rdquotag cloudrdquo portlet and the rdquorecent commentsrdquo portlet We will develop theseportlets by extending the [CPortlet] widget provided by Yii

In this section we will develop our first concrete portlet - the user menu portlet whichdisplays a list of menu items that are only available to authenticated users The menucontains four items

bull Approve Comments a hyperlink that leads to a list of comments pending approval

bull Create New Post a hyperlink that leads to the post creation page

bull Manage Posts a hyperlink that leads to the post management page

bull Logout a link button that would log out the current user

521 Creating UserMenu Class

We create the UserMenu class to represent the logic part of the user menu portlet Theclass is saved in the file wwwrootblogprotectedcomponentsUserMenuphp which has thefollowing content

Yiiimport(rsquoziiwidgetsCPortletrsquo)

class UserMenu extends CPortlet

public function init()

$this-gttitle=CHtmlencode(Yiiapp()-gtuser-gtname)

parentinit()

42 5 Portlets

protected function renderContent()

$this-gtrender(rsquouserMenursquo)

The UserMenu class extends from the CPortlet class that we created previously It overridesboth the init() method and the renderContent() method of CPortlet The former setsthe portlet title to be the name of the current user the latter generates the portlet bodycontent by rendering a view named userMenu

Tip Notice that we have to explicitly include the CPortlet class by calling Yii

import() before we refer to it the first time This is because CPortlet is part of thezii project ndash the official extension library for Yii For performance considerationclasses in this project are not listed as core classes Therefore we have to import itbefore we use it the first time

522 Creating userMenu View

Next we create the userMenu view which is saved in the file wwwrootblogprotected

componentsviewsuserMenuphp

ltulgt

ltligtltphp echo CHtmllink(rsquoCreate New Postrsquoarray(rsquopostcreatersquo)) gtltligt

ltligtltphp echo CHtmllink(rsquoManage Postsrsquoarray(rsquopostadminrsquo)) gtltligt

ltligtltphp echo CHtmllink(rsquoApprove Commentsrsquoarray(rsquocommentindexrsquo))

rsquo (rsquo Commentmodel()-gtpendingCommentCount rsquo)rsquo gtltligt

ltligtltphp echo CHtmllink(rsquoLogoutrsquoarray(rsquositelogoutrsquo)) gtltligt

ltulgt

Info By default view files for a widget should be placed under the views sub-directory of the directory containing the widget class file The file name must bethe same as the view name

523 Using UserMenu Portlet

It is time for us to make use of our newly completed UserMenu portlet We modify thelayout view file wwwrootblogprotectedviewslayoutscolumn2php as follows

53 Creating Tag Cloud Portlet 43

ltdiv id=sidebargt

ltphp if(Yiiapp()-gtuser-gtisGuest) $this-gtwidget(rsquoUserMenursquo) gt

ltdivgt

In the above we call the widget() method to generate and execute an instance of theUserMenu class Because the portlet should only be displayed to authenticated users weonly call widget() when the isGuest property of the current user is false (meaning theuser is authenticated)

524 Testing UserMenu Portlet

Letrsquos test what we have so far

1 Open a browser window and enter the URL httpwwwexamplecomblogindex

php Verify that there is nothing displayed in the side bar section of the page

2 Click on the Login hyperlink and fill out the login form to login If successful verifythat the UserMenu portlet appears in the side bar and the portlet has the usernameas its title

3 Click on the rsquoLogoutrsquo hyperlink in the UserMenu portlet Verify that the logout actionis successful and the UserMenu portlet disappears

525 Summary

What we have created is a portlet that is highly reusable We can easily reuse it in adifferent project with little or no modification Moreover the design of this portlet followsclosely the philosophy that logic and presentation should be separated While we did notpoint this out in the previous sections such practice is used nearly everywhere in a typicalYii application

53 Creating Tag Cloud Portlet

Tag cloud displays a list of post tags with visual decorations hinting the popularity of eachindividual tag

531 Creating TagCloud Class

We create the TagCloud class in the file wwwrootblogprotectedcomponentsTagCloud

php The file has the following content

44 5 Portlets

Yiiimport(rsquoziiwidgetsCPortletrsquo)

class TagCloud extends CPortlet

public $title=rsquoTagsrsquo

public $maxTags=20

protected function renderContent()

$tags=Tagmodel()-gtfindTagWeights($this-gtmaxTags)

foreach($tags as $tag=gt$weight)

$link=CHtmllink(CHtmlencode($tag) array(rsquopostindexrsquorsquotagrsquo=gt$tag))

echo CHtmltag(rsquospanrsquo array(

rsquoclassrsquo=gtrsquotagrsquo

rsquostylersquo=gtfont-size$weightpt) $link)n

Unlike the UserMenu portlet the TagCloud portlet does not use a view Instead its presen-tation is done in the renderContent() method This is because the presentation does notcontain much HTML tags

We display each tag as a hyperlink to the post index page with the corresponding tagparameter The font size of each tag link is adjusted according to their relative weightamong other tags If a tag has higher frequency value than the other it will have a biggerfont size

532 Using TagCloud Portlet

Usage of the TagCloud portlet is very simple We modify the layout file wwwrootblog

protectedviewslayoutsmainphp as follows

ltdiv id=sidebargt

ltphp if(Yiiapp()-gtuser-gtisGuest) $this-gtwidget(rsquoUserMenursquo) gt

ltphp $this-gtwidget(rsquoTagCloudrsquo array(

rsquomaxTagsrsquo=gtYiiapp()-gtparams[rsquotagCloudCountrsquo]

)) gt

ltdivgt

54 Creating Recent Comments Portlet 45

54 Creating Recent Comments Portlet

In this section we create the last portlet that displays a list of comments recently pub-lished

541 Creating RecentComments Class

We create the RecentComments class in the file wwwrootblogprotectedcomponentsRecentCommentsphp The file has the following content

Yiiimport(rsquoziiwidgetsCPortletrsquo)

class RecentComments extends CPortlet

public $title=rsquoRecent Commentsrsquo

public $maxComments=10

public function getRecentComments()

return Commentmodel()-gtfindRecentComments($this-gtmaxComments)

protected function renderContent()

$this-gtrender(rsquorecentCommentsrsquo)

In the above we invoke the findRecentComments method which is defined in the Comment

class as follows

class Comment extends CActiveRecord

public function findRecentComments($limit=10)

return $this-gtwith(rsquopostrsquo)-gtfindAll(array(

rsquoconditionrsquo=gtrsquotstatus=rsquoselfSTATUS APPROVED

rsquoorderrsquo=gtrsquotcreate time DESCrsquo

rsquolimitrsquo=gt$limit

))

46 5 Portlets

542 Creating recentComments View

The recentComments view is saved in the file wwwrootblogprotectedcomponentsviews

recentCommentsphp It simply displays every comment returned by the RecentComments

getRecentComments() method

543 Using RecentComments Portlet

We modify the layout file wwwrootblogprotectedviewslayoutsmainphp to embedthis last portlet

ltdiv id=sidebargt

ltphp if(Yiiapp()-gtuser-gtisGuest) $this-gtwidget(rsquoUserMenursquo) gt

ltphp $this-gtwidget(rsquoTagCloudrsquo array(

rsquomaxTagsrsquo=gtYiiapp()-gtparams[rsquotagCloudCountrsquo]

)) gt

ltphp $this-gtwidget(rsquoRecentCommentsrsquo array(

rsquomaxCommentsrsquo=gtYiiapp()-gtparams[rsquorecentCommentCountrsquo]

)) gt

ltdivgt

Chapter 6

Final Work

61 Beautifying URLs

The URLs linking various pages of our blog application currently look ugly For examplethe URL for the page showing a post looks like the following

indexphpr=postshowampid=1

In this section we describe how to beautify these URLs and make them SEO-friendlyOur goal is to be able to use the following URLs in the application

1 indexphppostsyii leads to the page showing a list of posts with tag yii

2 indexphppost2A+Test+Post leads to the page showing the detail of the postwith ID 2 whose title is A Test Post

3 indexphppostupdate1 leads to the page that allows updating the post with ID1

Note that in the second URL format we include the post title in the URL This is mainlyto make the URL SEO friendly It is said that search engines may also respect the wordsfound in a URL when it is being indexed

To achieve our goal we modify the application configuration as follows

return array(

rsquocomponentsrsquo=gtarray(

rsquourlManagerrsquo=gtarray(

rsquourlFormatrsquo=gtrsquopathrsquo

rsquorulesrsquo=gtarray(

48 6 Final Work

rsquopostltidd+gtlttitlegtrsquo=gtrsquopostviewrsquorsquopostslttaggtrsquo=gtrsquopostindexrsquo

)

)

)

)

In the above we configure the urlManager component by setting its urlFormat propertyto be path and adding a set of rules

The rules are used by urlManager to parse and create the URLs in the desired formatFor example the first rule says that if a URL indexphppostsyii is requested theurlManager component should be responsible to dispatch the request to the route post

index and generate a tag GET parameter with the value yii On the other hand whencreating a URL with the route postindex and parameter tag the urlManager componentwill also use this rule to generate the desired URL indexphppostsyii For this reasonwe say that urlManager is a two-way URL manager

The urlManager component can further beautify our URLs such as hiding indexphp inthe URLs appending suffix like html to the URLs We can obtain these features easilyby configuring various properties of urlManager in the application configuration For moredetails please refer to the Guide

62 Logging Errors

A production Web application often needs sophisticated logging for various events In ourblog application we would like to log the errors occurring when it is being used Sucherrors could be programming mistakes or usersrsquo misuse of the system Logging these errorswill help us to improve the blog application

We enable the error logging by modifying the application configuration as follows

return array(

rsquopreloadrsquo=gtarray(rsquologrsquo)

rsquocomponentsrsquo=gtarray(

rsquologrsquo=gtarray(

rsquoclassrsquo=gtrsquoCLogRouterrsquo

rsquoroutesrsquo=gtarray(

array(

rsquoclassrsquo=gtrsquoCFileLogRoutersquo

rsquolevelsrsquo=gtrsquoerror warningrsquo

63 Final Tune-up and Deployment 49

)

)

)

)

)

With the above configuration if an error or warning occurs detailed information will belogged and saved in a file located under the directory wwwrootblogprotectedruntime

The log component offers more advanced features such as sending log messages to a listof email addresses displaying log messages in JavaScript console window etc For moredetails please refer to the Guide

63 Final Tune-up and Deployment

We are close to finish our blog application Before deployment we would like to do sometune-ups

631 Changing Home Page

We change to use the post list page as the home page We modify the application config-uration as follows

return array(

rsquodefaultControllerrsquo=gtrsquopostrsquo

)

Tip Because PostController already declares list to be its default action whenwe access the home page of the application we will see the result generated by thelist action of the post controller

632 Enabling Schema Caching

Because ActiveRecord relies on the metadata about tables to determine the column infor-mation it takes time to read the metadata and analyze it This may not be a problemduring development stage but for an application running in production mode it is a totalwaste of time if the database schema does not change Therefore we should enable theschema caching by modifying the application configuration as follows

50 6 Final Work

return array(

rsquocomponentsrsquo=gtarray(

rsquocachersquo=gtarray(

rsquoclassrsquo=gtrsquoCDbCachersquo

)

rsquodbrsquo=gtarray(

rsquoclassrsquo=gtrsquosystemdbCDbConnectionrsquo

rsquoconnectionStringrsquo=gtrsquosqlitewwwrootblogprotecteddatablogdbrsquo

rsquoschemaCachingDurationrsquo=gt3600

)

)

)

In the above we first add a cache component which uses a default SQLite database as thecaching storage If our server is equipped with other caching extensions such as APCwe could change to use them as well We also modify the db component by setting itsschemaCachingDuration property to be 3600 which means the parsed database schemadata can remain valid in cache for 3600 seconds

633 Disabling Debugging Mode

We modify the entry script file wwwrootblogindexphp by removing the line definingthe constant YII DEBUG This constant is useful during development stage because it allowsYii to display more debugging information when an error occurs However when theapplication is running in production mode displaying debugging information is not agood idea because it may contain sensitive information such as where the script file islocated and the content in the file etc

634 Deploying the Application

The final deployment process manly involves copying the directory wwwrootblog to thetarget directory The following checklist shows every needed step

1 Install Yii in the target place if it is not available

2 Copy the entire directory wwwrootblog to the target place

3 Edit the entry script file indexphp by pointing the $yii variable to the new Yiibootstrap file

4 Edit the file protectedyiicphp by setting the $yiic variable to be the new Yiiyiicphp file

64 Future Enhancements 51

5 Change the permission of the directories assets and protectedruntime so that theyare writable by the Web server process

64 Future Enhancements

641 Using a Theme

Without writing any code our blog application is already themeable To use a themewe mainly need to develop the theme by writing customized view files in the theme Forexample to use a theme named classic that uses a different page layout we would createa layout view file wwwrootblogthemesclassicviewslayoutsmainphp We also needto change the application configuration to indicate our choice of the classic theme

return array(

rsquothemersquo=gtrsquoclassicrsquo

)

642 Internationalization

We may also internationalize our blog application so that its pages can be displayed indifferent languages This mainly involves efforts in two aspects

First we may create view files in different languages For example for the list pageof PostController we can create a view file wwwrootblogprotectedviewspostzh cn

listphp When the application is configured to use simplified Chinese (the language codeis zh cn) Yii will automatically use this new view file instead of the original one

Second we may create message translations for those messages generated by code Themessage translations should be saved as files under the directory wwwrootblogprotected

messages We also need to modify the code where we use text strings by enclosing themin the method call Yiit()

For more details about internationalization please refer to the Guide

643 Improving Performance with Cache

While the Yii framework itself is very efficient it is not necessarily true that an applicationwritten in Yii efficient There are several places in our blog application that we can improvethe performance For example the tag clould portlet could be one of the performancebottlenecks because it involves complex database query and PHP logic

52 6 Final Work

We can make use of the sophisticated caching feature provided by Yii to improve theperformance One of the most useful components in Yii is COutputCache which caches afragment of page display so that the underlying code generating the fragment does not needto be executed for every request For example in the layout file wwwrootblogprotected

viewslayoutsmainphp we can enclose the tag cloud portlet with COutputCache

ltphp if($this-gtbeginCache(rsquotagCloudrsquo array(rsquodurationrsquo=gt3600))) gt

ltphp $this-gtwidget(rsquoTagCloudrsquo array(

rsquomaxTagsrsquo=gtYiiapp()-gtparams[rsquotagCloudCountrsquo]

)) gt

ltphp $this-gtendCache() gt

With the above code the tag cloud display will be served from cache instead of beinggenerated on-the-fly for every request The cached content will remain valid in cache for3600 seconds

644 Adding New Features

Our blog application only has very basic functionalities To become a complete blogsystem more features are needed for example calendar portlet email notifications postcategorization archived post portlet and so on We will leave the implementation of thesefeatures to interested readers

  • Contents
  • License
  • Getting Started
    • Building a Blog System using Yii
    • Testdriving with Yii
      • Installing Yii
      • Creating Skeleton Application
      • Application Workflow
        • Requirements Analysis
        • Overall Design
          • Initial Prototyping
            • Setting Up Database
              • Creating Database
              • Establishing Database Connection
                • Scaffolding
                • Authenticating User
                • Summary
                  • Post Management
                    • Customizing Post Model
                      • Customizing rules() Method
                      • Customizing relations() Method
                      • Representing Status in Text
                        • Creating and Updating Posts
                          • Customizing Access Control
                          • Customizing create and update Operations
                            • Displaying Posts
                              • Customizing view Operation
                              • Customizing index Operation
                                • Managing Posts
                                  • Listing Posts in Tabular View
                                  • Deleting Posts
                                      • Comment Management
                                        • Customizing Comment Model
                                          • Customizing rules() Method
                                          • Customizing attributeLabels() Method
                                          • Customizing Saving Process
                                            • Creating and Displaying Comments
                                              • Displaying Comments
                                              • Creating Comments
                                                • Managing Comments
                                                  • Updating and Deleting Comments
                                                  • Approving Comments
                                                      • Portlets
                                                        • Customizing Page Layout
                                                        • Creating User Menu Portlet
                                                          • Creating UserMenu Class
                                                          • Creating userMenu View
                                                          • Using UserMenu Portlet
                                                          • Testing UserMenu Portlet
                                                          • Summary
                                                            • Creating Tag Cloud Portlet
                                                              • Creating TagCloud Class
                                                              • Using TagCloud Portlet
                                                                • Creating Recent Comments Portlet
                                                                  • Creating RecentComments Class
                                                                  • Creating recentComments View
                                                                  • Using RecentComments Portlet
                                                                      • Final Work
                                                                        • Beautifying URLs
                                                                        • Logging Errors
                                                                        • Final Tune-up and Deployment
                                                                          • Changing Home Page
                                                                          • Enabling Schema Caching
                                                                          • Disabling Debugging Mode
                                                                          • Deploying the Application
                                                                            • Future Enhancements
                                                                              • Using a Theme
                                                                              • Internationalization
                                                                              • Improving Performance with Cache
                                                                              • Adding New Features
Page 28: Yii Blog 1.1.0 Demo Tutor

31 Customizing Post Model 21

public function relations()

return array(

rsquoauthorrsquo =gt array(selfBELONGS TO rsquoUserrsquo rsquoauthor idrsquo)

rsquocommentsrsquo =gt array(selfHAS MANY rsquoCommentrsquo rsquopost idrsquo

rsquoconditionrsquo=gtrsquocommentsstatus=rsquoCommentSTATUS APPROVED

rsquoorderrsquo=gtrsquocommentscreate time DESCrsquo)

rsquocommentCountrsquo =gt array(selfSTAT rsquoCommentrsquo rsquopost idrsquo

rsquoconditionrsquo=gtrsquostatus=rsquoCommentSTATUS APPROVED)

)

The above relations state that

bull A post belongs to an author whose class is User and the relationship is establishedbased on the author id attribute value of the post

bull A post has many comments whose class is Comment and the relationship is establishedbased on the post id attribute value of the comments These comments should besorted according to their creation time and the comments must be approved

bull The commentCount relation is a bit special as it returns back an aggregation resultwhich is about how many comments the post has

With the above relation declaration we can easily access the author and comments of apost like the following

$author=$post-gtauthor

echo $author-gtusername

$comments=$post-gtcomments

foreach($comments as $comment)

echo $comment-gtcontent

For more details about how to declare and use relations please refer to the Guide

313 Representing Status in Text

Because the status of a post is stored as an integer in the database we need to provide atextual representation so that it is more intuitive when being displayed to end users In alarge system the similar requirement is very common

22 3 Post Management

As a generic solution we use the tbl lookup table to store the mapping between integervalues and textual representations that are needed by other data objects We modify theLookup model class as follows to more easily access the textual data in the table

class Lookup extends CActiveRecord

private static $ items=array()

public static function items($type)

if(isset(self$ items[$type]))

selfloadItems($type)

return self$ items[$type]

public static function item($type$code)

if(isset(self$ items[$type]))

selfloadItems($type)

return isset(self$ items[$type][$code]) self$ items[$type][$code] false

private static function loadItems($type)

self$ items[$type]=array()

$models=selfmodel()-gtfindAll(array(

rsquoconditionrsquo=gtrsquotype=typersquo

rsquoparamsrsquo=gtarray(rsquotypersquo=gt$type)

rsquoorderrsquo=gtrsquopositionrsquo

))

foreach($models as $model)

self$ items[$type][$model-gtcode]=$model-gtname

Our new code mainly provides two static methods Lookupitems() and Lookupitem()The former returns a list of strings belonging to the specified data type while the latterreturns a particular string for the given data type and data value

Our blog database is pre-populated with two lookup types PostStatus and CommentStatusThe former refers to the possible post statuses while the latter the comment statuses

In order to make our code easier to read we also declare a set of constants to representthe status integer values We should use these constants through our code when referringto the corresponding status values

32 Creating and Updating Posts 23

class Post extends CActiveRecord

const STATUS DRAFT=0

const STATUS PUBLISHED=1

const STATUS ARCHIVED=2

Therefore we can call Lookupitems(rsquoPostStatusrsquo) to get the list of possible post statuses(text strings indexed by the corresponding integer values) and call Lookupitem(rsquoPostStatusrsquoPostSTATUS PUBLISHED) to get the string representation of the published status

32 Creating and Updating Posts

With the Post model ready we need to fine-tune the actions and views for the controllerPostController In this section we first customize the access control of CRUD operationswe then modify the code implementing the create and update operations

321 Customizing Access Control

The first thing we want to do is to customize the access control because the code generatedby yiic does not fit our needs

We modify the accessRules() method in the file wwwrootblogprotectedcontrollers

PostControllerphp as follows

public function accessRules()

return array(

array(rsquoallowrsquo allow all users to perform rsquolistrsquo and rsquoshowrsquo actions

rsquoactionsrsquo=gtarray(rsquoindexrsquo rsquoviewrsquo)

rsquousersrsquo=gtarray(rsquorsquo)

)

array(rsquoallowrsquo allow authenticated users to perform any action

rsquousersrsquo=gtarray(rsquorsquo)

)

array(rsquodenyrsquo deny all users

rsquousersrsquo=gtarray(rsquorsquo)

)

)

The above rules state that all users can access the index and view actions and authenti-cated users can access any actions including the admin action The user should be denied

24 3 Post Management

access in any other scenario Note that these rules are evaluated in the order they arelisted here The first rule matching the current context makes the access decision Forexample if the current user is the system owner who tries to visit the post creation pagethe second rule will match and it will give the access to the user

322 Customizing create and update Operations

The create and update operations are very similar They both need to display an HTMLform to collect user inputs validate them and save them into database The main differ-ence is that the update operation will pre-populate the form with the existing post datafound in the database For this reason the yiic tool generates a partial view wwwroot

blogprotectedviewspost formphp that is embedded in both the create and update

views to render the needed HTML form

We first change the formphp file so that the HTML form only collects the inputs wewant title content tags and status We use plain text fields to collect inputs for thefirst three attributes and a dropdown list to collect input for status The dropdown listoptions are the text displays of the possible post statuses

ltphp echo CHtmlactiveDropDownList($postrsquostatusrsquoLookupitems(rsquoPostStatusrsquo)) gt

In the above we call Lookupitems(rsquoPostStatusrsquo) to bring back the list of post statuses

We then modify the Post class so that it can automatically set some attributes (eg create

time author id) before a post is saved to the database We override the beforeSave()

method as follows

protected function beforeSave()

if(parentbeforeSave())

if($this-gtisNewRecord)

$this-gtcreate time=$this-gtupdate time=time()

$this-gtauthor id=Yiiapp()-gtuser-gtid

else

$this-gtupdate time=time()

return true

else

return false

33 Displaying Posts 25

When we save a post we want to update the tbl tag table to reflect the change of tagfrequencies We can do this work in the afterSave() method which is automaticallyinvoked by Yii after a post is successfully saved into the database

protected function afterSave()

parentafterSave()

Tagmodel()-gtupdateFrequency($this-gt oldTags $this-gttags)

private $ oldTags

protected function afterFind()

parentafterFind()

$this-gt oldTags=$this-gttags

In the implementation because we want to detect if the user changes the tags in case he isupdating an existing post we need to know what the old tags are For this reason we alsowrite the afterFind() method to keep the old tags in the variable oldTags The methodafterFind() is invoked automatically by Yii when an AR record is populated with thedata from database

We are not going to give details of the TagupdateFrequency() method here Interestedreaders may refer to the file wwwrootyiidemosblogprotectedmodelsTagphp

33 Displaying Posts

In our blog application a post may be displayed among a list of posts or by itself Theformer is implemented as the index operation while the latter the view operation In thissection we customize both operations to fulfill our initial requirements

331 Customizing view Operation

The view operation is implemented by the actionView() method in PostController Itsdisplay is generated by the view view with the view file wwwrootblogprotectedviews

postviewphp

Below is the relevant code implementing the view operation in PostController

public function actionView()

26 3 Post Management

$this-gtrender(rsquoviewrsquoarray(

$post=$this-gtloadModel()

$this-gtrender(rsquoviewrsquoarray(

rsquomodelrsquo=gt$post

))

))

private $ model

public function loadModel()

if($this-gt model===null)

if(isset($ GET[rsquoidrsquo]))

if(Yiiapp()-gtuser-gtisGuest)

$condition=rsquostatus=rsquoPostSTATUS PUBLISHED

rsquo OR status=rsquoPOSTSTATUS ARCHIVED

else

$condition=rsquorsquo

$this-gt model=Postmodel()-gtfindbyPk($ GET[rsquoidrsquo] $condition)

if($this-gt model===null)

throw new CHttpException(404rsquoThe requested page does not existrsquo)

return $this-gt model

Our change mainly lies in the loadModel() method In this method we query the Post

table according to the id GET parameter If the post is not found or if it is not publishedor archived (when the user is a guest) we will throw a 404 HTTP error Otherwise thepost object is returned to actionView() which in turn passes the post object to the viewscript for further display

Tip Yii captures HTTP exceptions (instances of CHttpException) and displaysthem in either predefined templates or customized error views The skeleton appli-cation generated by yiic already contains a customized error view in wwwroot

blogprotectedviewssiteerrorphp We can modify this file if we want tofurther customize the error display

The change in the view script is mainly about ajdusting the formatting and styles of thepost display We will not go into details here Interested readers may refer to wwwroot

blogprotectedviewspostviewphp

33 Displaying Posts 27

332 Customizing index Operation

Like the view operation we customize the index operation in two places the actionIndex()

method in PostController and the view file wwwrootblogprotectedviewspostindex

php We mainly need to add the support for displaying a list of posts that are associatedwith a specified tag

Below is the modified actionIndex() method in PostController

public function actionIndex()

$criteria=new CDbCriteria(array(

rsquoconditionrsquo=gtrsquostatus=rsquoPostSTATUS PUBLISHED

rsquoorderrsquo=gtrsquoupdate time DESCrsquo

rsquowithrsquo=gtrsquocommentCountrsquo

))

if(isset($ GET[rsquotagrsquo]))

$criteria-gtaddSearchCondition(rsquotagsrsquo$ GET[rsquotagrsquo])

$dataProvider=new CActiveDataProvider(rsquoPostrsquo array(

rsquopaginationrsquo=gtarray(

rsquopageSizersquo=gtYiiapp()-gtparams[rsquopostsPerPagersquo]

)

rsquocriteriarsquo=gt$criteria

))

$this-gtrender(rsquoindexrsquoarray(

rsquodataProviderrsquo=gt$dataProvider

))

In the above we first create a query criteria for retrieving post list The criteria statesthat only published posts should be returned and they should be sorted according to theirupdate time in descending order Because when displaying a post in the list we want toshow how many comments the post has received in the criteria we also specify to bringback commentCount which if you remember is a relation declared in Postrelations()

In case when a user wants to see posts with a specific tag we would add a search conditionto the criteria to look for the specified tag

Using the query criteria we create a data provider which mainly serves for three purposesFirst it does pagination of the data when too many results may be returned Herewe customize the pagination by setting the page size to be an application parameterpostsPerPage Second it does sorting according to the user request And finally it feedsthe paginated and sorted data to widgets or view code for presentation

28 3 Post Management

After we finish with actionIndex() we modify the index view as follows Our change ismainly about adding the h1 header when the user specifies to display posts with a tag

ltphp if(empty($ GET[rsquotagrsquo])) gt

lth1gtPosts Tagged with ltigtltphp echo CHtmlencode($ GET[rsquotagrsquo]) gtltigtlth1gt

ltphp endif gt

ltphp $this-gtwidget(rsquoziiwidgetsCListViewrsquo array(

rsquodataProviderrsquo=gt$dataProvider

rsquoitemViewrsquo=gtrsquo viewrsquo

rsquotemplatersquo=gtitemsnpager)) gt

Note that in the above we use [CListView] to display the post list This widget requires apartial view to display the detail of each individual post Here we specify the partial viewto be view which means the file wwwrootblogprotectedviewspost viewphp In thisview script we can acccess the post instance being displayed via a local variable named$data

34 Managing Posts

Managing posts mainly refers to listing posts in an administrative view that allows us tosee posts with all statuses updating them and deleting them They are accomplished bythe admin operation and the delete operation respectively The code generated by yiic

does not need much modification Below we mainly explain how these two operations areimplemented

341 Listing Posts in Tabular View

The admin operation shows posts with all statuses in a tabular view The view supportssorting and pagination The following is the actionAdmin() method in PostController

public function actionAdmin()

$dataProvider=new CActiveDataProvider(rsquoPostrsquo array(

rsquosortrsquo=gtarray(

rsquodefaultOrderrsquo=gtrsquostatus update time DESCrsquo

)

))

$this-gtrender(rsquoadminrsquoarray(

rsquodataProviderrsquo=gt$dataProvider

))

34 Managing Posts 29

The above code is very similar to that in actionIndex() The main difference is in thequery criteria Here we do not specify any particular query criteria since we want to seeall posts We do customize the sort property of the data provider so that by default theposts are shown in the order of their statuses and update time

Below is the code for the admin view

lth1gtManage Postslth1gt

ltphp $this-gtwidget(rsquoziiwidgetsgridCGridViewrsquo array(

rsquodataProviderrsquo=gt$dataProvider

rsquocolumnsrsquo=gtarray(

array(

rsquonamersquo=gtrsquotitlersquo

rsquotypersquo=gtrsquorawrsquo

rsquovaluersquo=gtrsquoCHtmllink(CHtmlencode($data-gttitle) $data-gturl)rsquo

)

array(

rsquonamersquo=gtrsquostatusrsquo

rsquovaluersquo=gtrsquoLookupitem(PostStatus$data-gtstatus)rsquo

)

rsquocreate timedatetimersquo

rsquoupdate timedatetimersquo

array(

rsquoclassrsquo=gtrsquoCButtonColumnrsquo

)

)

)) gt

We use [CGridView] to display the posts It allows us to sort by a column and paginatethrough the posts if there are too many to be displayed in a single page Our change ismainly about how to display each column For example for the title column we specifythat it should be displayed as a hyperlink that points to the detailed view of the post

Tip When displaying text we call CHtmlencode() to encode HTML entities init This prevents from cross-site scripting attack

342 Deleting Posts

In the admin data grid there is a delete button in each row Clicking on the button shoulddelete the corresponding post Internally this triggers the delete action implemented asfollows

30 3 Post Management

public function actionDelete()

if(Yiiapp()-gtrequest-gtisPostRequest)

we only allow deletion via POST request

$this-gtloadModel()-gtdelete()

if(isset($ POST[rsquoajaxrsquo]))

$this-gtredirect(array(rsquoindexrsquo))

else

throw new CHttpException(400rsquoInvalid request Please do not repeat this request againrsquo)

The above code is the one generated by the yiic tool without any change We wouldlike to explain a little bit more about the checking on $ POST[rsquoajaxrsquo] The [CGridView]widget has a very nice feature that its sorting pagination and deletion operations are alldone in AJAX mode by default That means the whole page does not get reloaded if anyof the above operations is performed However it is also possible that the widget runs innon-AJAX mode (by setting its ajaxUpdate property to be false or disabling JavaScript onthe client side) It is necessary for the delete action to differentiate these two scenarios ifthe delete request is made via AJAX we should not redirect the user browser otherwisewe should

Deleting a post should also cause the deletion of all comments for that post In additionwe should also update the tbl tag table regarding the tags for the deleted post Both ofthese tasks can be achieved by writing an afterDelete method in the Post model class asfollows

protected function afterDelete()

parentafterDelete()

Commentmodel()-gtdeleteAll(rsquopost id=rsquo$this-gtid)

Tagmodel()-gtupdateFrequency($this-gttags rsquorsquo)

The above code is very straightforward it first deletes all those comments whose post id

is the same as the ID of the deleted post it then updates the tbl tag table for the tags

of the deleted post

34 Managing Posts 31

Tip We have to explicitly delete all comments for the deleted post here becauseSQLite does not really support foreign key constraints In a DBMS that supportsthis constraint (such as MySQL PostgreSQL) the foreign key constraint can be setup such that the DBMS automatically deletes the related comments if the post isdeleted In that case we no longer this explicit deletion call in our code

32 3 Post Management

Chapter 4

Comment Management

41 Customizing Comment Model

For the Comment model we mainly need to customize the rules() and attributeLabels()

methods The attributeLabels() method returns a mapping between attribute namesand attribute labels We do not need to touch relations() since the code generated bythe yiic tool is good enough

411 Customizing rules() Method

We first customize the validation rules generated by the yiic tool The following rules areused for comments

public function rules()

return array(

array(rsquocontent author emailrsquo rsquorequiredrsquo)

array(rsquoauthor email urlrsquo rsquolengthrsquo rsquomaxrsquo=gt128)

array(rsquoemailrsquorsquoemailrsquo)

array(rsquourlrsquorsquourlrsquo)

)

In the above we specify that the author email and content attributes are required thelength of author email and url cannot exceed 128 the email attribute must be a validemail address and the url attribute must be a valid URL

412 Customizing attributeLabels() Method

We then customize the attributeLabels() method to declare the label display for eachmodel attribute This method returns an array consisting of name-label pairs When wecall CHtmlactiveLabel() to display an attribute label

34 4 Comment Management

public function attributeLabels()

return array(

rsquoidrsquo =gt rsquoIdrsquo

rsquocontentrsquo =gt rsquoCommentrsquo

rsquostatusrsquo =gt rsquoStatusrsquo

rsquocreate timersquo =gt rsquoCreate Timersquo

rsquoauthorrsquo =gt rsquoNamersquo

rsquoemailrsquo =gt rsquoEmailrsquo

rsquourlrsquo =gt rsquoWebsitersquo

rsquopost idrsquo =gt rsquoPostrsquo

)

Tip If the label for an attribute is not declared in attributeLabels() an algo-rithm will be used to generate an appropriate label For example a label VerifyCode will be generated for attributes verify code or verifyCode

413 Customizing Saving Process

Because we want to record the creation time of a comment we override the beforeSave()

method of Comment like we do for the Post model

protected function beforeSave()

if(parentbeforeSave())

if($this-gtisNewRecord)

$this-gtcreate time=time()

return true

else

return false

42 Creating and Displaying Comments

In this section we implement the comment display and creation features

421 Displaying Comments

Instead of displaying and creating comments on individual pages we use the post detailpage (generated by the view action of PostController) Below the post content display

42 Creating and Displaying Comments 35

we display first a list of comments belonging to that post and then a comment creationform

In order to display comments on the post detail page we modify the view script wwwrootblogprotectedviewspostviewphp as follows

ltdiv id=commentsgt

ltphp if($model-gtcommentCountgt=1) gt

lth3gt

ltphp echo $model-gtcommentCount rsquocomment(s)rsquo gt

lth3gt

ltphp $this-gtrenderPartial(rsquo commentsrsquoarray(

rsquopostrsquo=gt$model

rsquocommentsrsquo=gt$model-gtcomments

)) gt

ltphp endif gt

ltdivgt

In the above we call renderPartial() to render a partial view named comments to displaythe list of comments belonging to the current post Note that in the view we use theexpression $model-gtcomments to retrieve the comments for the post This is valid becausewe have declared a comments relation in the Post class Evaluating this expression wouldtrigger an implicit JOIN database query to bring back the proper comments This featureis known as lazy relational query

The partial view comments is not very interesting It mainly goes through every commentand displays the detail of it Interested readers may refer to wwwrootyiidemosblog

protectedpost commentsphp

422 Creating Comments

To handle comment creation we first modify the actionView() method of PostController

as follows

public function actionView()

$post=$this-gtloadModel()

$comment=$this-gtnewComment($post)

$this-gtrender(rsquoviewrsquoarray(

rsquomodelrsquo=gt$post

rsquocommentrsquo=gt$comment

))

36 4 Comment Management

protected function newComment($post)

$comment=new Comment

if(isset($ POST[rsquoCommentrsquo]))

$comment-gtattributes=$ POST[rsquoCommentrsquo]

if($post-gtaddComment($comment))

if($comment-gtstatus==CommentSTATUS PENDING)

Yiiapp()-gtuser-gtsetFlash(rsquocommentSubmittedrsquorsquoThank yoursquo)

$this-gtrefresh()

return $comment

In the above we call the newComment() method before we render view In the newComment()

method we generate a Comment instance and check if the comment form is submitted If sowe try to add the comment for the post by calling $post-gtaddComment($comment) If it goesthrough we refresh the post detail page In case the comment needs to be approved we willshow a flash message to indicate this decision A flash message is usually a confirmationmessage displayed to end users If the user clicks on the refresh button of his browser themessage will disappear

We also need to modify wwwrootblogprotectedviewspostviewphp furthermore

ltdiv id=commentsgt

lth3gtLeave a Commentlth3gt

ltphp if(Yiiapp()-gtuser-gthasFlash(rsquocommentSubmittedrsquo)) gt

ltdiv class=successgt

ltphp echo Yiiapp()-gtuser-gtgetFlash(rsquocommentSubmittedrsquo) gt

ltdivgt

ltphp else gt

ltphp $this-gtrenderPartial(rsquocomment formrsquoarray(

rsquomodelrsquo=gt$comment

)) gt

ltphp endif gt

ltdivgtlt-- comments --gt

43 Managing Comments 37

In the above code we display the flash message if it is available If not we displaythe comment input form by rendering the partial view wwwrootblogprotectedviews

comment formphp

43 Managing Comments

Comment management includes updating deleting and approving comments These op-erations are implemented as actions in the CommentController class

431 Updating and Deleting Comments

The code generated by yiic for updating and deleting comments remains largely un-changed

432 Approving Comments

When comments are newly created they are in pending approval status and need to beapproved in order to be visible to guest users Approving a comment is mainly aboutchanging the status column of the comment

We create an actionApprove() method in CommentController as follows

public function actionApprove()

if(Yiiapp()-gtrequest-gtisPostRequest)

$comment=$this-gtloadModel()

$comment-gtapprove()

$this-gtredirect(array(rsquoindexrsquo))

else

throw new CHttpException(400rsquoInvalid requestrsquo)

In the above when the approve action is invoked via a POST request we call the approve()

method defined in the Comment model to change the status We then redirect the userbrowser to the page displaying the post that this comment belongs to

We also modify the actionIndex() method of Comment to show all comments We wouldlike to see comments pending approved to show up first

public function actionIndex()

38 4 Comment Management

$dataProvider=new CActiveDataProvider(rsquoCommentrsquo array(

rsquocriteriarsquo=gtarray(

rsquowithrsquo=gtrsquopostrsquo

rsquoorderrsquo=gtrsquotstatus tcreate time DESCrsquo

)

))

$this-gtrender(rsquoindexrsquoarray(

rsquodataProviderrsquo=gt$dataProvider

))

Notice that in the above code because both tbl post and tbl comment have columns statusand create time we need to disambiguate the corresponding column reference by prefixingthem with table alias names As described in the guide the alias for the primary table ina relational query is always t Therefore we are prefixing t to the status and create time

columns in the above code

Like the post index view the index view for CommentController uses [CListView] to displaythe comment list which in turn uses the partial view wwwrootblogprotectedviews

comment viewphp to display the detail of each individual comment We will not go intodetails here Interested readers may refer to the corresponding file in the blog demowwwrootyiidemosblogprotectedviewscomment viewphp

Chapter 5

Portlets

51 Customizing Page Layout

Features like rdquothe most recent commentsrdquo rdquotag cloudrdquo are better to be implemented inportlets A portlet is a pluggable user interface component that renders a fragment ofHTML code In this section we describe how to set up the portlet architecture for ourblog application

Before we go into details about developing portlets we first need to adjust our page layoutso that the portlets can be placed in a side bar

A page layout determines how to share common sections (eg header footer) amongdifferent pages and how to embed contents that are specific for different pages In Yiia page layout is represented as a view under the protectedviewslayouts folder Whenwe call the render() method in a controller the rendering result will be automaticallyembedded into this layout view before it is displayed to end users The default layoutview is named as main which means the wwwrootblogprotectedviewslayoutsmain

php file in our blog application

We could directly modify the main layout to add a side bar However doing so wouldmake some irrelevant pages such as about contact pages to have a side bar which is notalways desirable For this reason we need different layouts for different pages

We thus create two different layouts column1 and column2 The former displays the pagein a single column while the latter displays two columns in the page one of which is theside bar We will use column2 layout for the post and comment pages and column1 forthe rest of the pages Because both column1 and column2 still share the same header andfooter sections we let them to share the main layout

Based on the above description we create the column1 layout as follows

ltphp $this-gtbeginContent(rsquolayoutsmainrsquo) gt

ltdiv class=containergt

40 5 Portlets

ltdiv id=contentgt

ltphp echo $content gt

ltdivgtlt-- content --gt

ltdivgt

ltphp $this-gtendContent() gt

and the column2 layout as follows

ltphp $this-gtbeginContent(rsquolayoutsmainrsquo) gt

ltdiv class=containergt

ltdiv class=span-18gt

ltdiv id=contentgt

ltphp echo $content gt

ltdivgtlt-- content --gt

ltdivgt

ltdiv class=span-6 lastgt

ltdiv id=sidebargt

portlets to be placed here

ltdivgtlt-- sidebar --gt

ltdivgt

ltdivgt

ltphp $this-gtendContent() gt

As we can see both column1 and column2 are enclosed in a pair of beginContent andendContent calls They essentially use the specified view layoutsmain to decorate theenclosed content In all these layout views the variable $content refers to the dynamiccontent to be embedded into the layout

Besides customizing the layout view file we also need to adjust the CSS file wwwroot

blogcssmaincss so that the overall appearance would look like what we see in the blogdemo We will not go into details here

Info The skeleton application generated by the yiic tool uses the BluePrint CSSframework It provides a nice font reset and a robust grid system For more detailson how to use this framework please refer to its documentation

After creating the new layouts we need to set the layout property of controllers to makeuse of them So far we have three controllers SiteController PostController andCommentController We set SiteController to use column1 as the default layout whilethe rest two of the controllers use column2 for example

class PostController extends Controller

52 Creating User Menu Portlet 41

public $layout=rsquocolumn2rsquo

Tip If some view of the controller does not use the same layout as declaredby the layout property it may customize it in the view by a simple assignment$this-gtlayout=rsquonewlayoutrsquo

52 Creating User Menu Portlet

Based on the requirements analysis we need three different portlets the rdquouser menurdquoportlet the rdquotag cloudrdquo portlet and the rdquorecent commentsrdquo portlet We will develop theseportlets by extending the [CPortlet] widget provided by Yii

In this section we will develop our first concrete portlet - the user menu portlet whichdisplays a list of menu items that are only available to authenticated users The menucontains four items

bull Approve Comments a hyperlink that leads to a list of comments pending approval

bull Create New Post a hyperlink that leads to the post creation page

bull Manage Posts a hyperlink that leads to the post management page

bull Logout a link button that would log out the current user

521 Creating UserMenu Class

We create the UserMenu class to represent the logic part of the user menu portlet Theclass is saved in the file wwwrootblogprotectedcomponentsUserMenuphp which has thefollowing content

Yiiimport(rsquoziiwidgetsCPortletrsquo)

class UserMenu extends CPortlet

public function init()

$this-gttitle=CHtmlencode(Yiiapp()-gtuser-gtname)

parentinit()

42 5 Portlets

protected function renderContent()

$this-gtrender(rsquouserMenursquo)

The UserMenu class extends from the CPortlet class that we created previously It overridesboth the init() method and the renderContent() method of CPortlet The former setsthe portlet title to be the name of the current user the latter generates the portlet bodycontent by rendering a view named userMenu

Tip Notice that we have to explicitly include the CPortlet class by calling Yii

import() before we refer to it the first time This is because CPortlet is part of thezii project ndash the official extension library for Yii For performance considerationclasses in this project are not listed as core classes Therefore we have to import itbefore we use it the first time

522 Creating userMenu View

Next we create the userMenu view which is saved in the file wwwrootblogprotected

componentsviewsuserMenuphp

ltulgt

ltligtltphp echo CHtmllink(rsquoCreate New Postrsquoarray(rsquopostcreatersquo)) gtltligt

ltligtltphp echo CHtmllink(rsquoManage Postsrsquoarray(rsquopostadminrsquo)) gtltligt

ltligtltphp echo CHtmllink(rsquoApprove Commentsrsquoarray(rsquocommentindexrsquo))

rsquo (rsquo Commentmodel()-gtpendingCommentCount rsquo)rsquo gtltligt

ltligtltphp echo CHtmllink(rsquoLogoutrsquoarray(rsquositelogoutrsquo)) gtltligt

ltulgt

Info By default view files for a widget should be placed under the views sub-directory of the directory containing the widget class file The file name must bethe same as the view name

523 Using UserMenu Portlet

It is time for us to make use of our newly completed UserMenu portlet We modify thelayout view file wwwrootblogprotectedviewslayoutscolumn2php as follows

53 Creating Tag Cloud Portlet 43

ltdiv id=sidebargt

ltphp if(Yiiapp()-gtuser-gtisGuest) $this-gtwidget(rsquoUserMenursquo) gt

ltdivgt

In the above we call the widget() method to generate and execute an instance of theUserMenu class Because the portlet should only be displayed to authenticated users weonly call widget() when the isGuest property of the current user is false (meaning theuser is authenticated)

524 Testing UserMenu Portlet

Letrsquos test what we have so far

1 Open a browser window and enter the URL httpwwwexamplecomblogindex

php Verify that there is nothing displayed in the side bar section of the page

2 Click on the Login hyperlink and fill out the login form to login If successful verifythat the UserMenu portlet appears in the side bar and the portlet has the usernameas its title

3 Click on the rsquoLogoutrsquo hyperlink in the UserMenu portlet Verify that the logout actionis successful and the UserMenu portlet disappears

525 Summary

What we have created is a portlet that is highly reusable We can easily reuse it in adifferent project with little or no modification Moreover the design of this portlet followsclosely the philosophy that logic and presentation should be separated While we did notpoint this out in the previous sections such practice is used nearly everywhere in a typicalYii application

53 Creating Tag Cloud Portlet

Tag cloud displays a list of post tags with visual decorations hinting the popularity of eachindividual tag

531 Creating TagCloud Class

We create the TagCloud class in the file wwwrootblogprotectedcomponentsTagCloud

php The file has the following content

44 5 Portlets

Yiiimport(rsquoziiwidgetsCPortletrsquo)

class TagCloud extends CPortlet

public $title=rsquoTagsrsquo

public $maxTags=20

protected function renderContent()

$tags=Tagmodel()-gtfindTagWeights($this-gtmaxTags)

foreach($tags as $tag=gt$weight)

$link=CHtmllink(CHtmlencode($tag) array(rsquopostindexrsquorsquotagrsquo=gt$tag))

echo CHtmltag(rsquospanrsquo array(

rsquoclassrsquo=gtrsquotagrsquo

rsquostylersquo=gtfont-size$weightpt) $link)n

Unlike the UserMenu portlet the TagCloud portlet does not use a view Instead its presen-tation is done in the renderContent() method This is because the presentation does notcontain much HTML tags

We display each tag as a hyperlink to the post index page with the corresponding tagparameter The font size of each tag link is adjusted according to their relative weightamong other tags If a tag has higher frequency value than the other it will have a biggerfont size

532 Using TagCloud Portlet

Usage of the TagCloud portlet is very simple We modify the layout file wwwrootblog

protectedviewslayoutsmainphp as follows

ltdiv id=sidebargt

ltphp if(Yiiapp()-gtuser-gtisGuest) $this-gtwidget(rsquoUserMenursquo) gt

ltphp $this-gtwidget(rsquoTagCloudrsquo array(

rsquomaxTagsrsquo=gtYiiapp()-gtparams[rsquotagCloudCountrsquo]

)) gt

ltdivgt

54 Creating Recent Comments Portlet 45

54 Creating Recent Comments Portlet

In this section we create the last portlet that displays a list of comments recently pub-lished

541 Creating RecentComments Class

We create the RecentComments class in the file wwwrootblogprotectedcomponentsRecentCommentsphp The file has the following content

Yiiimport(rsquoziiwidgetsCPortletrsquo)

class RecentComments extends CPortlet

public $title=rsquoRecent Commentsrsquo

public $maxComments=10

public function getRecentComments()

return Commentmodel()-gtfindRecentComments($this-gtmaxComments)

protected function renderContent()

$this-gtrender(rsquorecentCommentsrsquo)

In the above we invoke the findRecentComments method which is defined in the Comment

class as follows

class Comment extends CActiveRecord

public function findRecentComments($limit=10)

return $this-gtwith(rsquopostrsquo)-gtfindAll(array(

rsquoconditionrsquo=gtrsquotstatus=rsquoselfSTATUS APPROVED

rsquoorderrsquo=gtrsquotcreate time DESCrsquo

rsquolimitrsquo=gt$limit

))

46 5 Portlets

542 Creating recentComments View

The recentComments view is saved in the file wwwrootblogprotectedcomponentsviews

recentCommentsphp It simply displays every comment returned by the RecentComments

getRecentComments() method

543 Using RecentComments Portlet

We modify the layout file wwwrootblogprotectedviewslayoutsmainphp to embedthis last portlet

ltdiv id=sidebargt

ltphp if(Yiiapp()-gtuser-gtisGuest) $this-gtwidget(rsquoUserMenursquo) gt

ltphp $this-gtwidget(rsquoTagCloudrsquo array(

rsquomaxTagsrsquo=gtYiiapp()-gtparams[rsquotagCloudCountrsquo]

)) gt

ltphp $this-gtwidget(rsquoRecentCommentsrsquo array(

rsquomaxCommentsrsquo=gtYiiapp()-gtparams[rsquorecentCommentCountrsquo]

)) gt

ltdivgt

Chapter 6

Final Work

61 Beautifying URLs

The URLs linking various pages of our blog application currently look ugly For examplethe URL for the page showing a post looks like the following

indexphpr=postshowampid=1

In this section we describe how to beautify these URLs and make them SEO-friendlyOur goal is to be able to use the following URLs in the application

1 indexphppostsyii leads to the page showing a list of posts with tag yii

2 indexphppost2A+Test+Post leads to the page showing the detail of the postwith ID 2 whose title is A Test Post

3 indexphppostupdate1 leads to the page that allows updating the post with ID1

Note that in the second URL format we include the post title in the URL This is mainlyto make the URL SEO friendly It is said that search engines may also respect the wordsfound in a URL when it is being indexed

To achieve our goal we modify the application configuration as follows

return array(

rsquocomponentsrsquo=gtarray(

rsquourlManagerrsquo=gtarray(

rsquourlFormatrsquo=gtrsquopathrsquo

rsquorulesrsquo=gtarray(

48 6 Final Work

rsquopostltidd+gtlttitlegtrsquo=gtrsquopostviewrsquorsquopostslttaggtrsquo=gtrsquopostindexrsquo

)

)

)

)

In the above we configure the urlManager component by setting its urlFormat propertyto be path and adding a set of rules

The rules are used by urlManager to parse and create the URLs in the desired formatFor example the first rule says that if a URL indexphppostsyii is requested theurlManager component should be responsible to dispatch the request to the route post

index and generate a tag GET parameter with the value yii On the other hand whencreating a URL with the route postindex and parameter tag the urlManager componentwill also use this rule to generate the desired URL indexphppostsyii For this reasonwe say that urlManager is a two-way URL manager

The urlManager component can further beautify our URLs such as hiding indexphp inthe URLs appending suffix like html to the URLs We can obtain these features easilyby configuring various properties of urlManager in the application configuration For moredetails please refer to the Guide

62 Logging Errors

A production Web application often needs sophisticated logging for various events In ourblog application we would like to log the errors occurring when it is being used Sucherrors could be programming mistakes or usersrsquo misuse of the system Logging these errorswill help us to improve the blog application

We enable the error logging by modifying the application configuration as follows

return array(

rsquopreloadrsquo=gtarray(rsquologrsquo)

rsquocomponentsrsquo=gtarray(

rsquologrsquo=gtarray(

rsquoclassrsquo=gtrsquoCLogRouterrsquo

rsquoroutesrsquo=gtarray(

array(

rsquoclassrsquo=gtrsquoCFileLogRoutersquo

rsquolevelsrsquo=gtrsquoerror warningrsquo

63 Final Tune-up and Deployment 49

)

)

)

)

)

With the above configuration if an error or warning occurs detailed information will belogged and saved in a file located under the directory wwwrootblogprotectedruntime

The log component offers more advanced features such as sending log messages to a listof email addresses displaying log messages in JavaScript console window etc For moredetails please refer to the Guide

63 Final Tune-up and Deployment

We are close to finish our blog application Before deployment we would like to do sometune-ups

631 Changing Home Page

We change to use the post list page as the home page We modify the application config-uration as follows

return array(

rsquodefaultControllerrsquo=gtrsquopostrsquo

)

Tip Because PostController already declares list to be its default action whenwe access the home page of the application we will see the result generated by thelist action of the post controller

632 Enabling Schema Caching

Because ActiveRecord relies on the metadata about tables to determine the column infor-mation it takes time to read the metadata and analyze it This may not be a problemduring development stage but for an application running in production mode it is a totalwaste of time if the database schema does not change Therefore we should enable theschema caching by modifying the application configuration as follows

50 6 Final Work

return array(

rsquocomponentsrsquo=gtarray(

rsquocachersquo=gtarray(

rsquoclassrsquo=gtrsquoCDbCachersquo

)

rsquodbrsquo=gtarray(

rsquoclassrsquo=gtrsquosystemdbCDbConnectionrsquo

rsquoconnectionStringrsquo=gtrsquosqlitewwwrootblogprotecteddatablogdbrsquo

rsquoschemaCachingDurationrsquo=gt3600

)

)

)

In the above we first add a cache component which uses a default SQLite database as thecaching storage If our server is equipped with other caching extensions such as APCwe could change to use them as well We also modify the db component by setting itsschemaCachingDuration property to be 3600 which means the parsed database schemadata can remain valid in cache for 3600 seconds

633 Disabling Debugging Mode

We modify the entry script file wwwrootblogindexphp by removing the line definingthe constant YII DEBUG This constant is useful during development stage because it allowsYii to display more debugging information when an error occurs However when theapplication is running in production mode displaying debugging information is not agood idea because it may contain sensitive information such as where the script file islocated and the content in the file etc

634 Deploying the Application

The final deployment process manly involves copying the directory wwwrootblog to thetarget directory The following checklist shows every needed step

1 Install Yii in the target place if it is not available

2 Copy the entire directory wwwrootblog to the target place

3 Edit the entry script file indexphp by pointing the $yii variable to the new Yiibootstrap file

4 Edit the file protectedyiicphp by setting the $yiic variable to be the new Yiiyiicphp file

64 Future Enhancements 51

5 Change the permission of the directories assets and protectedruntime so that theyare writable by the Web server process

64 Future Enhancements

641 Using a Theme

Without writing any code our blog application is already themeable To use a themewe mainly need to develop the theme by writing customized view files in the theme Forexample to use a theme named classic that uses a different page layout we would createa layout view file wwwrootblogthemesclassicviewslayoutsmainphp We also needto change the application configuration to indicate our choice of the classic theme

return array(

rsquothemersquo=gtrsquoclassicrsquo

)

642 Internationalization

We may also internationalize our blog application so that its pages can be displayed indifferent languages This mainly involves efforts in two aspects

First we may create view files in different languages For example for the list pageof PostController we can create a view file wwwrootblogprotectedviewspostzh cn

listphp When the application is configured to use simplified Chinese (the language codeis zh cn) Yii will automatically use this new view file instead of the original one

Second we may create message translations for those messages generated by code Themessage translations should be saved as files under the directory wwwrootblogprotected

messages We also need to modify the code where we use text strings by enclosing themin the method call Yiit()

For more details about internationalization please refer to the Guide

643 Improving Performance with Cache

While the Yii framework itself is very efficient it is not necessarily true that an applicationwritten in Yii efficient There are several places in our blog application that we can improvethe performance For example the tag clould portlet could be one of the performancebottlenecks because it involves complex database query and PHP logic

52 6 Final Work

We can make use of the sophisticated caching feature provided by Yii to improve theperformance One of the most useful components in Yii is COutputCache which caches afragment of page display so that the underlying code generating the fragment does not needto be executed for every request For example in the layout file wwwrootblogprotected

viewslayoutsmainphp we can enclose the tag cloud portlet with COutputCache

ltphp if($this-gtbeginCache(rsquotagCloudrsquo array(rsquodurationrsquo=gt3600))) gt

ltphp $this-gtwidget(rsquoTagCloudrsquo array(

rsquomaxTagsrsquo=gtYiiapp()-gtparams[rsquotagCloudCountrsquo]

)) gt

ltphp $this-gtendCache() gt

With the above code the tag cloud display will be served from cache instead of beinggenerated on-the-fly for every request The cached content will remain valid in cache for3600 seconds

644 Adding New Features

Our blog application only has very basic functionalities To become a complete blogsystem more features are needed for example calendar portlet email notifications postcategorization archived post portlet and so on We will leave the implementation of thesefeatures to interested readers

  • Contents
  • License
  • Getting Started
    • Building a Blog System using Yii
    • Testdriving with Yii
      • Installing Yii
      • Creating Skeleton Application
      • Application Workflow
        • Requirements Analysis
        • Overall Design
          • Initial Prototyping
            • Setting Up Database
              • Creating Database
              • Establishing Database Connection
                • Scaffolding
                • Authenticating User
                • Summary
                  • Post Management
                    • Customizing Post Model
                      • Customizing rules() Method
                      • Customizing relations() Method
                      • Representing Status in Text
                        • Creating and Updating Posts
                          • Customizing Access Control
                          • Customizing create and update Operations
                            • Displaying Posts
                              • Customizing view Operation
                              • Customizing index Operation
                                • Managing Posts
                                  • Listing Posts in Tabular View
                                  • Deleting Posts
                                      • Comment Management
                                        • Customizing Comment Model
                                          • Customizing rules() Method
                                          • Customizing attributeLabels() Method
                                          • Customizing Saving Process
                                            • Creating and Displaying Comments
                                              • Displaying Comments
                                              • Creating Comments
                                                • Managing Comments
                                                  • Updating and Deleting Comments
                                                  • Approving Comments
                                                      • Portlets
                                                        • Customizing Page Layout
                                                        • Creating User Menu Portlet
                                                          • Creating UserMenu Class
                                                          • Creating userMenu View
                                                          • Using UserMenu Portlet
                                                          • Testing UserMenu Portlet
                                                          • Summary
                                                            • Creating Tag Cloud Portlet
                                                              • Creating TagCloud Class
                                                              • Using TagCloud Portlet
                                                                • Creating Recent Comments Portlet
                                                                  • Creating RecentComments Class
                                                                  • Creating recentComments View
                                                                  • Using RecentComments Portlet
                                                                      • Final Work
                                                                        • Beautifying URLs
                                                                        • Logging Errors
                                                                        • Final Tune-up and Deployment
                                                                          • Changing Home Page
                                                                          • Enabling Schema Caching
                                                                          • Disabling Debugging Mode
                                                                          • Deploying the Application
                                                                            • Future Enhancements
                                                                              • Using a Theme
                                                                              • Internationalization
                                                                              • Improving Performance with Cache
                                                                              • Adding New Features
Page 29: Yii Blog 1.1.0 Demo Tutor

22 3 Post Management

As a generic solution we use the tbl lookup table to store the mapping between integervalues and textual representations that are needed by other data objects We modify theLookup model class as follows to more easily access the textual data in the table

class Lookup extends CActiveRecord

private static $ items=array()

public static function items($type)

if(isset(self$ items[$type]))

selfloadItems($type)

return self$ items[$type]

public static function item($type$code)

if(isset(self$ items[$type]))

selfloadItems($type)

return isset(self$ items[$type][$code]) self$ items[$type][$code] false

private static function loadItems($type)

self$ items[$type]=array()

$models=selfmodel()-gtfindAll(array(

rsquoconditionrsquo=gtrsquotype=typersquo

rsquoparamsrsquo=gtarray(rsquotypersquo=gt$type)

rsquoorderrsquo=gtrsquopositionrsquo

))

foreach($models as $model)

self$ items[$type][$model-gtcode]=$model-gtname

Our new code mainly provides two static methods Lookupitems() and Lookupitem()The former returns a list of strings belonging to the specified data type while the latterreturns a particular string for the given data type and data value

Our blog database is pre-populated with two lookup types PostStatus and CommentStatusThe former refers to the possible post statuses while the latter the comment statuses

In order to make our code easier to read we also declare a set of constants to representthe status integer values We should use these constants through our code when referringto the corresponding status values

32 Creating and Updating Posts 23

class Post extends CActiveRecord

const STATUS DRAFT=0

const STATUS PUBLISHED=1

const STATUS ARCHIVED=2

Therefore we can call Lookupitems(rsquoPostStatusrsquo) to get the list of possible post statuses(text strings indexed by the corresponding integer values) and call Lookupitem(rsquoPostStatusrsquoPostSTATUS PUBLISHED) to get the string representation of the published status

32 Creating and Updating Posts

With the Post model ready we need to fine-tune the actions and views for the controllerPostController In this section we first customize the access control of CRUD operationswe then modify the code implementing the create and update operations

321 Customizing Access Control

The first thing we want to do is to customize the access control because the code generatedby yiic does not fit our needs

We modify the accessRules() method in the file wwwrootblogprotectedcontrollers

PostControllerphp as follows

public function accessRules()

return array(

array(rsquoallowrsquo allow all users to perform rsquolistrsquo and rsquoshowrsquo actions

rsquoactionsrsquo=gtarray(rsquoindexrsquo rsquoviewrsquo)

rsquousersrsquo=gtarray(rsquorsquo)

)

array(rsquoallowrsquo allow authenticated users to perform any action

rsquousersrsquo=gtarray(rsquorsquo)

)

array(rsquodenyrsquo deny all users

rsquousersrsquo=gtarray(rsquorsquo)

)

)

The above rules state that all users can access the index and view actions and authenti-cated users can access any actions including the admin action The user should be denied

24 3 Post Management

access in any other scenario Note that these rules are evaluated in the order they arelisted here The first rule matching the current context makes the access decision Forexample if the current user is the system owner who tries to visit the post creation pagethe second rule will match and it will give the access to the user

322 Customizing create and update Operations

The create and update operations are very similar They both need to display an HTMLform to collect user inputs validate them and save them into database The main differ-ence is that the update operation will pre-populate the form with the existing post datafound in the database For this reason the yiic tool generates a partial view wwwroot

blogprotectedviewspost formphp that is embedded in both the create and update

views to render the needed HTML form

We first change the formphp file so that the HTML form only collects the inputs wewant title content tags and status We use plain text fields to collect inputs for thefirst three attributes and a dropdown list to collect input for status The dropdown listoptions are the text displays of the possible post statuses

ltphp echo CHtmlactiveDropDownList($postrsquostatusrsquoLookupitems(rsquoPostStatusrsquo)) gt

In the above we call Lookupitems(rsquoPostStatusrsquo) to bring back the list of post statuses

We then modify the Post class so that it can automatically set some attributes (eg create

time author id) before a post is saved to the database We override the beforeSave()

method as follows

protected function beforeSave()

if(parentbeforeSave())

if($this-gtisNewRecord)

$this-gtcreate time=$this-gtupdate time=time()

$this-gtauthor id=Yiiapp()-gtuser-gtid

else

$this-gtupdate time=time()

return true

else

return false

33 Displaying Posts 25

When we save a post we want to update the tbl tag table to reflect the change of tagfrequencies We can do this work in the afterSave() method which is automaticallyinvoked by Yii after a post is successfully saved into the database

protected function afterSave()

parentafterSave()

Tagmodel()-gtupdateFrequency($this-gt oldTags $this-gttags)

private $ oldTags

protected function afterFind()

parentafterFind()

$this-gt oldTags=$this-gttags

In the implementation because we want to detect if the user changes the tags in case he isupdating an existing post we need to know what the old tags are For this reason we alsowrite the afterFind() method to keep the old tags in the variable oldTags The methodafterFind() is invoked automatically by Yii when an AR record is populated with thedata from database

We are not going to give details of the TagupdateFrequency() method here Interestedreaders may refer to the file wwwrootyiidemosblogprotectedmodelsTagphp

33 Displaying Posts

In our blog application a post may be displayed among a list of posts or by itself Theformer is implemented as the index operation while the latter the view operation In thissection we customize both operations to fulfill our initial requirements

331 Customizing view Operation

The view operation is implemented by the actionView() method in PostController Itsdisplay is generated by the view view with the view file wwwrootblogprotectedviews

postviewphp

Below is the relevant code implementing the view operation in PostController

public function actionView()

26 3 Post Management

$this-gtrender(rsquoviewrsquoarray(

$post=$this-gtloadModel()

$this-gtrender(rsquoviewrsquoarray(

rsquomodelrsquo=gt$post

))

))

private $ model

public function loadModel()

if($this-gt model===null)

if(isset($ GET[rsquoidrsquo]))

if(Yiiapp()-gtuser-gtisGuest)

$condition=rsquostatus=rsquoPostSTATUS PUBLISHED

rsquo OR status=rsquoPOSTSTATUS ARCHIVED

else

$condition=rsquorsquo

$this-gt model=Postmodel()-gtfindbyPk($ GET[rsquoidrsquo] $condition)

if($this-gt model===null)

throw new CHttpException(404rsquoThe requested page does not existrsquo)

return $this-gt model

Our change mainly lies in the loadModel() method In this method we query the Post

table according to the id GET parameter If the post is not found or if it is not publishedor archived (when the user is a guest) we will throw a 404 HTTP error Otherwise thepost object is returned to actionView() which in turn passes the post object to the viewscript for further display

Tip Yii captures HTTP exceptions (instances of CHttpException) and displaysthem in either predefined templates or customized error views The skeleton appli-cation generated by yiic already contains a customized error view in wwwroot

blogprotectedviewssiteerrorphp We can modify this file if we want tofurther customize the error display

The change in the view script is mainly about ajdusting the formatting and styles of thepost display We will not go into details here Interested readers may refer to wwwroot

blogprotectedviewspostviewphp

33 Displaying Posts 27

332 Customizing index Operation

Like the view operation we customize the index operation in two places the actionIndex()

method in PostController and the view file wwwrootblogprotectedviewspostindex

php We mainly need to add the support for displaying a list of posts that are associatedwith a specified tag

Below is the modified actionIndex() method in PostController

public function actionIndex()

$criteria=new CDbCriteria(array(

rsquoconditionrsquo=gtrsquostatus=rsquoPostSTATUS PUBLISHED

rsquoorderrsquo=gtrsquoupdate time DESCrsquo

rsquowithrsquo=gtrsquocommentCountrsquo

))

if(isset($ GET[rsquotagrsquo]))

$criteria-gtaddSearchCondition(rsquotagsrsquo$ GET[rsquotagrsquo])

$dataProvider=new CActiveDataProvider(rsquoPostrsquo array(

rsquopaginationrsquo=gtarray(

rsquopageSizersquo=gtYiiapp()-gtparams[rsquopostsPerPagersquo]

)

rsquocriteriarsquo=gt$criteria

))

$this-gtrender(rsquoindexrsquoarray(

rsquodataProviderrsquo=gt$dataProvider

))

In the above we first create a query criteria for retrieving post list The criteria statesthat only published posts should be returned and they should be sorted according to theirupdate time in descending order Because when displaying a post in the list we want toshow how many comments the post has received in the criteria we also specify to bringback commentCount which if you remember is a relation declared in Postrelations()

In case when a user wants to see posts with a specific tag we would add a search conditionto the criteria to look for the specified tag

Using the query criteria we create a data provider which mainly serves for three purposesFirst it does pagination of the data when too many results may be returned Herewe customize the pagination by setting the page size to be an application parameterpostsPerPage Second it does sorting according to the user request And finally it feedsthe paginated and sorted data to widgets or view code for presentation

28 3 Post Management

After we finish with actionIndex() we modify the index view as follows Our change ismainly about adding the h1 header when the user specifies to display posts with a tag

ltphp if(empty($ GET[rsquotagrsquo])) gt

lth1gtPosts Tagged with ltigtltphp echo CHtmlencode($ GET[rsquotagrsquo]) gtltigtlth1gt

ltphp endif gt

ltphp $this-gtwidget(rsquoziiwidgetsCListViewrsquo array(

rsquodataProviderrsquo=gt$dataProvider

rsquoitemViewrsquo=gtrsquo viewrsquo

rsquotemplatersquo=gtitemsnpager)) gt

Note that in the above we use [CListView] to display the post list This widget requires apartial view to display the detail of each individual post Here we specify the partial viewto be view which means the file wwwrootblogprotectedviewspost viewphp In thisview script we can acccess the post instance being displayed via a local variable named$data

34 Managing Posts

Managing posts mainly refers to listing posts in an administrative view that allows us tosee posts with all statuses updating them and deleting them They are accomplished bythe admin operation and the delete operation respectively The code generated by yiic

does not need much modification Below we mainly explain how these two operations areimplemented

341 Listing Posts in Tabular View

The admin operation shows posts with all statuses in a tabular view The view supportssorting and pagination The following is the actionAdmin() method in PostController

public function actionAdmin()

$dataProvider=new CActiveDataProvider(rsquoPostrsquo array(

rsquosortrsquo=gtarray(

rsquodefaultOrderrsquo=gtrsquostatus update time DESCrsquo

)

))

$this-gtrender(rsquoadminrsquoarray(

rsquodataProviderrsquo=gt$dataProvider

))

34 Managing Posts 29

The above code is very similar to that in actionIndex() The main difference is in thequery criteria Here we do not specify any particular query criteria since we want to seeall posts We do customize the sort property of the data provider so that by default theposts are shown in the order of their statuses and update time

Below is the code for the admin view

lth1gtManage Postslth1gt

ltphp $this-gtwidget(rsquoziiwidgetsgridCGridViewrsquo array(

rsquodataProviderrsquo=gt$dataProvider

rsquocolumnsrsquo=gtarray(

array(

rsquonamersquo=gtrsquotitlersquo

rsquotypersquo=gtrsquorawrsquo

rsquovaluersquo=gtrsquoCHtmllink(CHtmlencode($data-gttitle) $data-gturl)rsquo

)

array(

rsquonamersquo=gtrsquostatusrsquo

rsquovaluersquo=gtrsquoLookupitem(PostStatus$data-gtstatus)rsquo

)

rsquocreate timedatetimersquo

rsquoupdate timedatetimersquo

array(

rsquoclassrsquo=gtrsquoCButtonColumnrsquo

)

)

)) gt

We use [CGridView] to display the posts It allows us to sort by a column and paginatethrough the posts if there are too many to be displayed in a single page Our change ismainly about how to display each column For example for the title column we specifythat it should be displayed as a hyperlink that points to the detailed view of the post

Tip When displaying text we call CHtmlencode() to encode HTML entities init This prevents from cross-site scripting attack

342 Deleting Posts

In the admin data grid there is a delete button in each row Clicking on the button shoulddelete the corresponding post Internally this triggers the delete action implemented asfollows

30 3 Post Management

public function actionDelete()

if(Yiiapp()-gtrequest-gtisPostRequest)

we only allow deletion via POST request

$this-gtloadModel()-gtdelete()

if(isset($ POST[rsquoajaxrsquo]))

$this-gtredirect(array(rsquoindexrsquo))

else

throw new CHttpException(400rsquoInvalid request Please do not repeat this request againrsquo)

The above code is the one generated by the yiic tool without any change We wouldlike to explain a little bit more about the checking on $ POST[rsquoajaxrsquo] The [CGridView]widget has a very nice feature that its sorting pagination and deletion operations are alldone in AJAX mode by default That means the whole page does not get reloaded if anyof the above operations is performed However it is also possible that the widget runs innon-AJAX mode (by setting its ajaxUpdate property to be false or disabling JavaScript onthe client side) It is necessary for the delete action to differentiate these two scenarios ifthe delete request is made via AJAX we should not redirect the user browser otherwisewe should

Deleting a post should also cause the deletion of all comments for that post In additionwe should also update the tbl tag table regarding the tags for the deleted post Both ofthese tasks can be achieved by writing an afterDelete method in the Post model class asfollows

protected function afterDelete()

parentafterDelete()

Commentmodel()-gtdeleteAll(rsquopost id=rsquo$this-gtid)

Tagmodel()-gtupdateFrequency($this-gttags rsquorsquo)

The above code is very straightforward it first deletes all those comments whose post id

is the same as the ID of the deleted post it then updates the tbl tag table for the tags

of the deleted post

34 Managing Posts 31

Tip We have to explicitly delete all comments for the deleted post here becauseSQLite does not really support foreign key constraints In a DBMS that supportsthis constraint (such as MySQL PostgreSQL) the foreign key constraint can be setup such that the DBMS automatically deletes the related comments if the post isdeleted In that case we no longer this explicit deletion call in our code

32 3 Post Management

Chapter 4

Comment Management

41 Customizing Comment Model

For the Comment model we mainly need to customize the rules() and attributeLabels()

methods The attributeLabels() method returns a mapping between attribute namesand attribute labels We do not need to touch relations() since the code generated bythe yiic tool is good enough

411 Customizing rules() Method

We first customize the validation rules generated by the yiic tool The following rules areused for comments

public function rules()

return array(

array(rsquocontent author emailrsquo rsquorequiredrsquo)

array(rsquoauthor email urlrsquo rsquolengthrsquo rsquomaxrsquo=gt128)

array(rsquoemailrsquorsquoemailrsquo)

array(rsquourlrsquorsquourlrsquo)

)

In the above we specify that the author email and content attributes are required thelength of author email and url cannot exceed 128 the email attribute must be a validemail address and the url attribute must be a valid URL

412 Customizing attributeLabels() Method

We then customize the attributeLabels() method to declare the label display for eachmodel attribute This method returns an array consisting of name-label pairs When wecall CHtmlactiveLabel() to display an attribute label

34 4 Comment Management

public function attributeLabels()

return array(

rsquoidrsquo =gt rsquoIdrsquo

rsquocontentrsquo =gt rsquoCommentrsquo

rsquostatusrsquo =gt rsquoStatusrsquo

rsquocreate timersquo =gt rsquoCreate Timersquo

rsquoauthorrsquo =gt rsquoNamersquo

rsquoemailrsquo =gt rsquoEmailrsquo

rsquourlrsquo =gt rsquoWebsitersquo

rsquopost idrsquo =gt rsquoPostrsquo

)

Tip If the label for an attribute is not declared in attributeLabels() an algo-rithm will be used to generate an appropriate label For example a label VerifyCode will be generated for attributes verify code or verifyCode

413 Customizing Saving Process

Because we want to record the creation time of a comment we override the beforeSave()

method of Comment like we do for the Post model

protected function beforeSave()

if(parentbeforeSave())

if($this-gtisNewRecord)

$this-gtcreate time=time()

return true

else

return false

42 Creating and Displaying Comments

In this section we implement the comment display and creation features

421 Displaying Comments

Instead of displaying and creating comments on individual pages we use the post detailpage (generated by the view action of PostController) Below the post content display

42 Creating and Displaying Comments 35

we display first a list of comments belonging to that post and then a comment creationform

In order to display comments on the post detail page we modify the view script wwwrootblogprotectedviewspostviewphp as follows

ltdiv id=commentsgt

ltphp if($model-gtcommentCountgt=1) gt

lth3gt

ltphp echo $model-gtcommentCount rsquocomment(s)rsquo gt

lth3gt

ltphp $this-gtrenderPartial(rsquo commentsrsquoarray(

rsquopostrsquo=gt$model

rsquocommentsrsquo=gt$model-gtcomments

)) gt

ltphp endif gt

ltdivgt

In the above we call renderPartial() to render a partial view named comments to displaythe list of comments belonging to the current post Note that in the view we use theexpression $model-gtcomments to retrieve the comments for the post This is valid becausewe have declared a comments relation in the Post class Evaluating this expression wouldtrigger an implicit JOIN database query to bring back the proper comments This featureis known as lazy relational query

The partial view comments is not very interesting It mainly goes through every commentand displays the detail of it Interested readers may refer to wwwrootyiidemosblog

protectedpost commentsphp

422 Creating Comments

To handle comment creation we first modify the actionView() method of PostController

as follows

public function actionView()

$post=$this-gtloadModel()

$comment=$this-gtnewComment($post)

$this-gtrender(rsquoviewrsquoarray(

rsquomodelrsquo=gt$post

rsquocommentrsquo=gt$comment

))

36 4 Comment Management

protected function newComment($post)

$comment=new Comment

if(isset($ POST[rsquoCommentrsquo]))

$comment-gtattributes=$ POST[rsquoCommentrsquo]

if($post-gtaddComment($comment))

if($comment-gtstatus==CommentSTATUS PENDING)

Yiiapp()-gtuser-gtsetFlash(rsquocommentSubmittedrsquorsquoThank yoursquo)

$this-gtrefresh()

return $comment

In the above we call the newComment() method before we render view In the newComment()

method we generate a Comment instance and check if the comment form is submitted If sowe try to add the comment for the post by calling $post-gtaddComment($comment) If it goesthrough we refresh the post detail page In case the comment needs to be approved we willshow a flash message to indicate this decision A flash message is usually a confirmationmessage displayed to end users If the user clicks on the refresh button of his browser themessage will disappear

We also need to modify wwwrootblogprotectedviewspostviewphp furthermore

ltdiv id=commentsgt

lth3gtLeave a Commentlth3gt

ltphp if(Yiiapp()-gtuser-gthasFlash(rsquocommentSubmittedrsquo)) gt

ltdiv class=successgt

ltphp echo Yiiapp()-gtuser-gtgetFlash(rsquocommentSubmittedrsquo) gt

ltdivgt

ltphp else gt

ltphp $this-gtrenderPartial(rsquocomment formrsquoarray(

rsquomodelrsquo=gt$comment

)) gt

ltphp endif gt

ltdivgtlt-- comments --gt

43 Managing Comments 37

In the above code we display the flash message if it is available If not we displaythe comment input form by rendering the partial view wwwrootblogprotectedviews

comment formphp

43 Managing Comments

Comment management includes updating deleting and approving comments These op-erations are implemented as actions in the CommentController class

431 Updating and Deleting Comments

The code generated by yiic for updating and deleting comments remains largely un-changed

432 Approving Comments

When comments are newly created they are in pending approval status and need to beapproved in order to be visible to guest users Approving a comment is mainly aboutchanging the status column of the comment

We create an actionApprove() method in CommentController as follows

public function actionApprove()

if(Yiiapp()-gtrequest-gtisPostRequest)

$comment=$this-gtloadModel()

$comment-gtapprove()

$this-gtredirect(array(rsquoindexrsquo))

else

throw new CHttpException(400rsquoInvalid requestrsquo)

In the above when the approve action is invoked via a POST request we call the approve()

method defined in the Comment model to change the status We then redirect the userbrowser to the page displaying the post that this comment belongs to

We also modify the actionIndex() method of Comment to show all comments We wouldlike to see comments pending approved to show up first

public function actionIndex()

38 4 Comment Management

$dataProvider=new CActiveDataProvider(rsquoCommentrsquo array(

rsquocriteriarsquo=gtarray(

rsquowithrsquo=gtrsquopostrsquo

rsquoorderrsquo=gtrsquotstatus tcreate time DESCrsquo

)

))

$this-gtrender(rsquoindexrsquoarray(

rsquodataProviderrsquo=gt$dataProvider

))

Notice that in the above code because both tbl post and tbl comment have columns statusand create time we need to disambiguate the corresponding column reference by prefixingthem with table alias names As described in the guide the alias for the primary table ina relational query is always t Therefore we are prefixing t to the status and create time

columns in the above code

Like the post index view the index view for CommentController uses [CListView] to displaythe comment list which in turn uses the partial view wwwrootblogprotectedviews

comment viewphp to display the detail of each individual comment We will not go intodetails here Interested readers may refer to the corresponding file in the blog demowwwrootyiidemosblogprotectedviewscomment viewphp

Chapter 5

Portlets

51 Customizing Page Layout

Features like rdquothe most recent commentsrdquo rdquotag cloudrdquo are better to be implemented inportlets A portlet is a pluggable user interface component that renders a fragment ofHTML code In this section we describe how to set up the portlet architecture for ourblog application

Before we go into details about developing portlets we first need to adjust our page layoutso that the portlets can be placed in a side bar

A page layout determines how to share common sections (eg header footer) amongdifferent pages and how to embed contents that are specific for different pages In Yiia page layout is represented as a view under the protectedviewslayouts folder Whenwe call the render() method in a controller the rendering result will be automaticallyembedded into this layout view before it is displayed to end users The default layoutview is named as main which means the wwwrootblogprotectedviewslayoutsmain

php file in our blog application

We could directly modify the main layout to add a side bar However doing so wouldmake some irrelevant pages such as about contact pages to have a side bar which is notalways desirable For this reason we need different layouts for different pages

We thus create two different layouts column1 and column2 The former displays the pagein a single column while the latter displays two columns in the page one of which is theside bar We will use column2 layout for the post and comment pages and column1 forthe rest of the pages Because both column1 and column2 still share the same header andfooter sections we let them to share the main layout

Based on the above description we create the column1 layout as follows

ltphp $this-gtbeginContent(rsquolayoutsmainrsquo) gt

ltdiv class=containergt

40 5 Portlets

ltdiv id=contentgt

ltphp echo $content gt

ltdivgtlt-- content --gt

ltdivgt

ltphp $this-gtendContent() gt

and the column2 layout as follows

ltphp $this-gtbeginContent(rsquolayoutsmainrsquo) gt

ltdiv class=containergt

ltdiv class=span-18gt

ltdiv id=contentgt

ltphp echo $content gt

ltdivgtlt-- content --gt

ltdivgt

ltdiv class=span-6 lastgt

ltdiv id=sidebargt

portlets to be placed here

ltdivgtlt-- sidebar --gt

ltdivgt

ltdivgt

ltphp $this-gtendContent() gt

As we can see both column1 and column2 are enclosed in a pair of beginContent andendContent calls They essentially use the specified view layoutsmain to decorate theenclosed content In all these layout views the variable $content refers to the dynamiccontent to be embedded into the layout

Besides customizing the layout view file we also need to adjust the CSS file wwwroot

blogcssmaincss so that the overall appearance would look like what we see in the blogdemo We will not go into details here

Info The skeleton application generated by the yiic tool uses the BluePrint CSSframework It provides a nice font reset and a robust grid system For more detailson how to use this framework please refer to its documentation

After creating the new layouts we need to set the layout property of controllers to makeuse of them So far we have three controllers SiteController PostController andCommentController We set SiteController to use column1 as the default layout whilethe rest two of the controllers use column2 for example

class PostController extends Controller

52 Creating User Menu Portlet 41

public $layout=rsquocolumn2rsquo

Tip If some view of the controller does not use the same layout as declaredby the layout property it may customize it in the view by a simple assignment$this-gtlayout=rsquonewlayoutrsquo

52 Creating User Menu Portlet

Based on the requirements analysis we need three different portlets the rdquouser menurdquoportlet the rdquotag cloudrdquo portlet and the rdquorecent commentsrdquo portlet We will develop theseportlets by extending the [CPortlet] widget provided by Yii

In this section we will develop our first concrete portlet - the user menu portlet whichdisplays a list of menu items that are only available to authenticated users The menucontains four items

bull Approve Comments a hyperlink that leads to a list of comments pending approval

bull Create New Post a hyperlink that leads to the post creation page

bull Manage Posts a hyperlink that leads to the post management page

bull Logout a link button that would log out the current user

521 Creating UserMenu Class

We create the UserMenu class to represent the logic part of the user menu portlet Theclass is saved in the file wwwrootblogprotectedcomponentsUserMenuphp which has thefollowing content

Yiiimport(rsquoziiwidgetsCPortletrsquo)

class UserMenu extends CPortlet

public function init()

$this-gttitle=CHtmlencode(Yiiapp()-gtuser-gtname)

parentinit()

42 5 Portlets

protected function renderContent()

$this-gtrender(rsquouserMenursquo)

The UserMenu class extends from the CPortlet class that we created previously It overridesboth the init() method and the renderContent() method of CPortlet The former setsthe portlet title to be the name of the current user the latter generates the portlet bodycontent by rendering a view named userMenu

Tip Notice that we have to explicitly include the CPortlet class by calling Yii

import() before we refer to it the first time This is because CPortlet is part of thezii project ndash the official extension library for Yii For performance considerationclasses in this project are not listed as core classes Therefore we have to import itbefore we use it the first time

522 Creating userMenu View

Next we create the userMenu view which is saved in the file wwwrootblogprotected

componentsviewsuserMenuphp

ltulgt

ltligtltphp echo CHtmllink(rsquoCreate New Postrsquoarray(rsquopostcreatersquo)) gtltligt

ltligtltphp echo CHtmllink(rsquoManage Postsrsquoarray(rsquopostadminrsquo)) gtltligt

ltligtltphp echo CHtmllink(rsquoApprove Commentsrsquoarray(rsquocommentindexrsquo))

rsquo (rsquo Commentmodel()-gtpendingCommentCount rsquo)rsquo gtltligt

ltligtltphp echo CHtmllink(rsquoLogoutrsquoarray(rsquositelogoutrsquo)) gtltligt

ltulgt

Info By default view files for a widget should be placed under the views sub-directory of the directory containing the widget class file The file name must bethe same as the view name

523 Using UserMenu Portlet

It is time for us to make use of our newly completed UserMenu portlet We modify thelayout view file wwwrootblogprotectedviewslayoutscolumn2php as follows

53 Creating Tag Cloud Portlet 43

ltdiv id=sidebargt

ltphp if(Yiiapp()-gtuser-gtisGuest) $this-gtwidget(rsquoUserMenursquo) gt

ltdivgt

In the above we call the widget() method to generate and execute an instance of theUserMenu class Because the portlet should only be displayed to authenticated users weonly call widget() when the isGuest property of the current user is false (meaning theuser is authenticated)

524 Testing UserMenu Portlet

Letrsquos test what we have so far

1 Open a browser window and enter the URL httpwwwexamplecomblogindex

php Verify that there is nothing displayed in the side bar section of the page

2 Click on the Login hyperlink and fill out the login form to login If successful verifythat the UserMenu portlet appears in the side bar and the portlet has the usernameas its title

3 Click on the rsquoLogoutrsquo hyperlink in the UserMenu portlet Verify that the logout actionis successful and the UserMenu portlet disappears

525 Summary

What we have created is a portlet that is highly reusable We can easily reuse it in adifferent project with little or no modification Moreover the design of this portlet followsclosely the philosophy that logic and presentation should be separated While we did notpoint this out in the previous sections such practice is used nearly everywhere in a typicalYii application

53 Creating Tag Cloud Portlet

Tag cloud displays a list of post tags with visual decorations hinting the popularity of eachindividual tag

531 Creating TagCloud Class

We create the TagCloud class in the file wwwrootblogprotectedcomponentsTagCloud

php The file has the following content

44 5 Portlets

Yiiimport(rsquoziiwidgetsCPortletrsquo)

class TagCloud extends CPortlet

public $title=rsquoTagsrsquo

public $maxTags=20

protected function renderContent()

$tags=Tagmodel()-gtfindTagWeights($this-gtmaxTags)

foreach($tags as $tag=gt$weight)

$link=CHtmllink(CHtmlencode($tag) array(rsquopostindexrsquorsquotagrsquo=gt$tag))

echo CHtmltag(rsquospanrsquo array(

rsquoclassrsquo=gtrsquotagrsquo

rsquostylersquo=gtfont-size$weightpt) $link)n

Unlike the UserMenu portlet the TagCloud portlet does not use a view Instead its presen-tation is done in the renderContent() method This is because the presentation does notcontain much HTML tags

We display each tag as a hyperlink to the post index page with the corresponding tagparameter The font size of each tag link is adjusted according to their relative weightamong other tags If a tag has higher frequency value than the other it will have a biggerfont size

532 Using TagCloud Portlet

Usage of the TagCloud portlet is very simple We modify the layout file wwwrootblog

protectedviewslayoutsmainphp as follows

ltdiv id=sidebargt

ltphp if(Yiiapp()-gtuser-gtisGuest) $this-gtwidget(rsquoUserMenursquo) gt

ltphp $this-gtwidget(rsquoTagCloudrsquo array(

rsquomaxTagsrsquo=gtYiiapp()-gtparams[rsquotagCloudCountrsquo]

)) gt

ltdivgt

54 Creating Recent Comments Portlet 45

54 Creating Recent Comments Portlet

In this section we create the last portlet that displays a list of comments recently pub-lished

541 Creating RecentComments Class

We create the RecentComments class in the file wwwrootblogprotectedcomponentsRecentCommentsphp The file has the following content

Yiiimport(rsquoziiwidgetsCPortletrsquo)

class RecentComments extends CPortlet

public $title=rsquoRecent Commentsrsquo

public $maxComments=10

public function getRecentComments()

return Commentmodel()-gtfindRecentComments($this-gtmaxComments)

protected function renderContent()

$this-gtrender(rsquorecentCommentsrsquo)

In the above we invoke the findRecentComments method which is defined in the Comment

class as follows

class Comment extends CActiveRecord

public function findRecentComments($limit=10)

return $this-gtwith(rsquopostrsquo)-gtfindAll(array(

rsquoconditionrsquo=gtrsquotstatus=rsquoselfSTATUS APPROVED

rsquoorderrsquo=gtrsquotcreate time DESCrsquo

rsquolimitrsquo=gt$limit

))

46 5 Portlets

542 Creating recentComments View

The recentComments view is saved in the file wwwrootblogprotectedcomponentsviews

recentCommentsphp It simply displays every comment returned by the RecentComments

getRecentComments() method

543 Using RecentComments Portlet

We modify the layout file wwwrootblogprotectedviewslayoutsmainphp to embedthis last portlet

ltdiv id=sidebargt

ltphp if(Yiiapp()-gtuser-gtisGuest) $this-gtwidget(rsquoUserMenursquo) gt

ltphp $this-gtwidget(rsquoTagCloudrsquo array(

rsquomaxTagsrsquo=gtYiiapp()-gtparams[rsquotagCloudCountrsquo]

)) gt

ltphp $this-gtwidget(rsquoRecentCommentsrsquo array(

rsquomaxCommentsrsquo=gtYiiapp()-gtparams[rsquorecentCommentCountrsquo]

)) gt

ltdivgt

Chapter 6

Final Work

61 Beautifying URLs

The URLs linking various pages of our blog application currently look ugly For examplethe URL for the page showing a post looks like the following

indexphpr=postshowampid=1

In this section we describe how to beautify these URLs and make them SEO-friendlyOur goal is to be able to use the following URLs in the application

1 indexphppostsyii leads to the page showing a list of posts with tag yii

2 indexphppost2A+Test+Post leads to the page showing the detail of the postwith ID 2 whose title is A Test Post

3 indexphppostupdate1 leads to the page that allows updating the post with ID1

Note that in the second URL format we include the post title in the URL This is mainlyto make the URL SEO friendly It is said that search engines may also respect the wordsfound in a URL when it is being indexed

To achieve our goal we modify the application configuration as follows

return array(

rsquocomponentsrsquo=gtarray(

rsquourlManagerrsquo=gtarray(

rsquourlFormatrsquo=gtrsquopathrsquo

rsquorulesrsquo=gtarray(

48 6 Final Work

rsquopostltidd+gtlttitlegtrsquo=gtrsquopostviewrsquorsquopostslttaggtrsquo=gtrsquopostindexrsquo

)

)

)

)

In the above we configure the urlManager component by setting its urlFormat propertyto be path and adding a set of rules

The rules are used by urlManager to parse and create the URLs in the desired formatFor example the first rule says that if a URL indexphppostsyii is requested theurlManager component should be responsible to dispatch the request to the route post

index and generate a tag GET parameter with the value yii On the other hand whencreating a URL with the route postindex and parameter tag the urlManager componentwill also use this rule to generate the desired URL indexphppostsyii For this reasonwe say that urlManager is a two-way URL manager

The urlManager component can further beautify our URLs such as hiding indexphp inthe URLs appending suffix like html to the URLs We can obtain these features easilyby configuring various properties of urlManager in the application configuration For moredetails please refer to the Guide

62 Logging Errors

A production Web application often needs sophisticated logging for various events In ourblog application we would like to log the errors occurring when it is being used Sucherrors could be programming mistakes or usersrsquo misuse of the system Logging these errorswill help us to improve the blog application

We enable the error logging by modifying the application configuration as follows

return array(

rsquopreloadrsquo=gtarray(rsquologrsquo)

rsquocomponentsrsquo=gtarray(

rsquologrsquo=gtarray(

rsquoclassrsquo=gtrsquoCLogRouterrsquo

rsquoroutesrsquo=gtarray(

array(

rsquoclassrsquo=gtrsquoCFileLogRoutersquo

rsquolevelsrsquo=gtrsquoerror warningrsquo

63 Final Tune-up and Deployment 49

)

)

)

)

)

With the above configuration if an error or warning occurs detailed information will belogged and saved in a file located under the directory wwwrootblogprotectedruntime

The log component offers more advanced features such as sending log messages to a listof email addresses displaying log messages in JavaScript console window etc For moredetails please refer to the Guide

63 Final Tune-up and Deployment

We are close to finish our blog application Before deployment we would like to do sometune-ups

631 Changing Home Page

We change to use the post list page as the home page We modify the application config-uration as follows

return array(

rsquodefaultControllerrsquo=gtrsquopostrsquo

)

Tip Because PostController already declares list to be its default action whenwe access the home page of the application we will see the result generated by thelist action of the post controller

632 Enabling Schema Caching

Because ActiveRecord relies on the metadata about tables to determine the column infor-mation it takes time to read the metadata and analyze it This may not be a problemduring development stage but for an application running in production mode it is a totalwaste of time if the database schema does not change Therefore we should enable theschema caching by modifying the application configuration as follows

50 6 Final Work

return array(

rsquocomponentsrsquo=gtarray(

rsquocachersquo=gtarray(

rsquoclassrsquo=gtrsquoCDbCachersquo

)

rsquodbrsquo=gtarray(

rsquoclassrsquo=gtrsquosystemdbCDbConnectionrsquo

rsquoconnectionStringrsquo=gtrsquosqlitewwwrootblogprotecteddatablogdbrsquo

rsquoschemaCachingDurationrsquo=gt3600

)

)

)

In the above we first add a cache component which uses a default SQLite database as thecaching storage If our server is equipped with other caching extensions such as APCwe could change to use them as well We also modify the db component by setting itsschemaCachingDuration property to be 3600 which means the parsed database schemadata can remain valid in cache for 3600 seconds

633 Disabling Debugging Mode

We modify the entry script file wwwrootblogindexphp by removing the line definingthe constant YII DEBUG This constant is useful during development stage because it allowsYii to display more debugging information when an error occurs However when theapplication is running in production mode displaying debugging information is not agood idea because it may contain sensitive information such as where the script file islocated and the content in the file etc

634 Deploying the Application

The final deployment process manly involves copying the directory wwwrootblog to thetarget directory The following checklist shows every needed step

1 Install Yii in the target place if it is not available

2 Copy the entire directory wwwrootblog to the target place

3 Edit the entry script file indexphp by pointing the $yii variable to the new Yiibootstrap file

4 Edit the file protectedyiicphp by setting the $yiic variable to be the new Yiiyiicphp file

64 Future Enhancements 51

5 Change the permission of the directories assets and protectedruntime so that theyare writable by the Web server process

64 Future Enhancements

641 Using a Theme

Without writing any code our blog application is already themeable To use a themewe mainly need to develop the theme by writing customized view files in the theme Forexample to use a theme named classic that uses a different page layout we would createa layout view file wwwrootblogthemesclassicviewslayoutsmainphp We also needto change the application configuration to indicate our choice of the classic theme

return array(

rsquothemersquo=gtrsquoclassicrsquo

)

642 Internationalization

We may also internationalize our blog application so that its pages can be displayed indifferent languages This mainly involves efforts in two aspects

First we may create view files in different languages For example for the list pageof PostController we can create a view file wwwrootblogprotectedviewspostzh cn

listphp When the application is configured to use simplified Chinese (the language codeis zh cn) Yii will automatically use this new view file instead of the original one

Second we may create message translations for those messages generated by code Themessage translations should be saved as files under the directory wwwrootblogprotected

messages We also need to modify the code where we use text strings by enclosing themin the method call Yiit()

For more details about internationalization please refer to the Guide

643 Improving Performance with Cache

While the Yii framework itself is very efficient it is not necessarily true that an applicationwritten in Yii efficient There are several places in our blog application that we can improvethe performance For example the tag clould portlet could be one of the performancebottlenecks because it involves complex database query and PHP logic

52 6 Final Work

We can make use of the sophisticated caching feature provided by Yii to improve theperformance One of the most useful components in Yii is COutputCache which caches afragment of page display so that the underlying code generating the fragment does not needto be executed for every request For example in the layout file wwwrootblogprotected

viewslayoutsmainphp we can enclose the tag cloud portlet with COutputCache

ltphp if($this-gtbeginCache(rsquotagCloudrsquo array(rsquodurationrsquo=gt3600))) gt

ltphp $this-gtwidget(rsquoTagCloudrsquo array(

rsquomaxTagsrsquo=gtYiiapp()-gtparams[rsquotagCloudCountrsquo]

)) gt

ltphp $this-gtendCache() gt

With the above code the tag cloud display will be served from cache instead of beinggenerated on-the-fly for every request The cached content will remain valid in cache for3600 seconds

644 Adding New Features

Our blog application only has very basic functionalities To become a complete blogsystem more features are needed for example calendar portlet email notifications postcategorization archived post portlet and so on We will leave the implementation of thesefeatures to interested readers

  • Contents
  • License
  • Getting Started
    • Building a Blog System using Yii
    • Testdriving with Yii
      • Installing Yii
      • Creating Skeleton Application
      • Application Workflow
        • Requirements Analysis
        • Overall Design
          • Initial Prototyping
            • Setting Up Database
              • Creating Database
              • Establishing Database Connection
                • Scaffolding
                • Authenticating User
                • Summary
                  • Post Management
                    • Customizing Post Model
                      • Customizing rules() Method
                      • Customizing relations() Method
                      • Representing Status in Text
                        • Creating and Updating Posts
                          • Customizing Access Control
                          • Customizing create and update Operations
                            • Displaying Posts
                              • Customizing view Operation
                              • Customizing index Operation
                                • Managing Posts
                                  • Listing Posts in Tabular View
                                  • Deleting Posts
                                      • Comment Management
                                        • Customizing Comment Model
                                          • Customizing rules() Method
                                          • Customizing attributeLabels() Method
                                          • Customizing Saving Process
                                            • Creating and Displaying Comments
                                              • Displaying Comments
                                              • Creating Comments
                                                • Managing Comments
                                                  • Updating and Deleting Comments
                                                  • Approving Comments
                                                      • Portlets
                                                        • Customizing Page Layout
                                                        • Creating User Menu Portlet
                                                          • Creating UserMenu Class
                                                          • Creating userMenu View
                                                          • Using UserMenu Portlet
                                                          • Testing UserMenu Portlet
                                                          • Summary
                                                            • Creating Tag Cloud Portlet
                                                              • Creating TagCloud Class
                                                              • Using TagCloud Portlet
                                                                • Creating Recent Comments Portlet
                                                                  • Creating RecentComments Class
                                                                  • Creating recentComments View
                                                                  • Using RecentComments Portlet
                                                                      • Final Work
                                                                        • Beautifying URLs
                                                                        • Logging Errors
                                                                        • Final Tune-up and Deployment
                                                                          • Changing Home Page
                                                                          • Enabling Schema Caching
                                                                          • Disabling Debugging Mode
                                                                          • Deploying the Application
                                                                            • Future Enhancements
                                                                              • Using a Theme
                                                                              • Internationalization
                                                                              • Improving Performance with Cache
                                                                              • Adding New Features
Page 30: Yii Blog 1.1.0 Demo Tutor

32 Creating and Updating Posts 23

class Post extends CActiveRecord

const STATUS DRAFT=0

const STATUS PUBLISHED=1

const STATUS ARCHIVED=2

Therefore we can call Lookupitems(rsquoPostStatusrsquo) to get the list of possible post statuses(text strings indexed by the corresponding integer values) and call Lookupitem(rsquoPostStatusrsquoPostSTATUS PUBLISHED) to get the string representation of the published status

32 Creating and Updating Posts

With the Post model ready we need to fine-tune the actions and views for the controllerPostController In this section we first customize the access control of CRUD operationswe then modify the code implementing the create and update operations

321 Customizing Access Control

The first thing we want to do is to customize the access control because the code generatedby yiic does not fit our needs

We modify the accessRules() method in the file wwwrootblogprotectedcontrollers

PostControllerphp as follows

public function accessRules()

return array(

array(rsquoallowrsquo allow all users to perform rsquolistrsquo and rsquoshowrsquo actions

rsquoactionsrsquo=gtarray(rsquoindexrsquo rsquoviewrsquo)

rsquousersrsquo=gtarray(rsquorsquo)

)

array(rsquoallowrsquo allow authenticated users to perform any action

rsquousersrsquo=gtarray(rsquorsquo)

)

array(rsquodenyrsquo deny all users

rsquousersrsquo=gtarray(rsquorsquo)

)

)

The above rules state that all users can access the index and view actions and authenti-cated users can access any actions including the admin action The user should be denied

24 3 Post Management

access in any other scenario Note that these rules are evaluated in the order they arelisted here The first rule matching the current context makes the access decision Forexample if the current user is the system owner who tries to visit the post creation pagethe second rule will match and it will give the access to the user

322 Customizing create and update Operations

The create and update operations are very similar They both need to display an HTMLform to collect user inputs validate them and save them into database The main differ-ence is that the update operation will pre-populate the form with the existing post datafound in the database For this reason the yiic tool generates a partial view wwwroot

blogprotectedviewspost formphp that is embedded in both the create and update

views to render the needed HTML form

We first change the formphp file so that the HTML form only collects the inputs wewant title content tags and status We use plain text fields to collect inputs for thefirst three attributes and a dropdown list to collect input for status The dropdown listoptions are the text displays of the possible post statuses

ltphp echo CHtmlactiveDropDownList($postrsquostatusrsquoLookupitems(rsquoPostStatusrsquo)) gt

In the above we call Lookupitems(rsquoPostStatusrsquo) to bring back the list of post statuses

We then modify the Post class so that it can automatically set some attributes (eg create

time author id) before a post is saved to the database We override the beforeSave()

method as follows

protected function beforeSave()

if(parentbeforeSave())

if($this-gtisNewRecord)

$this-gtcreate time=$this-gtupdate time=time()

$this-gtauthor id=Yiiapp()-gtuser-gtid

else

$this-gtupdate time=time()

return true

else

return false

33 Displaying Posts 25

When we save a post we want to update the tbl tag table to reflect the change of tagfrequencies We can do this work in the afterSave() method which is automaticallyinvoked by Yii after a post is successfully saved into the database

protected function afterSave()

parentafterSave()

Tagmodel()-gtupdateFrequency($this-gt oldTags $this-gttags)

private $ oldTags

protected function afterFind()

parentafterFind()

$this-gt oldTags=$this-gttags

In the implementation because we want to detect if the user changes the tags in case he isupdating an existing post we need to know what the old tags are For this reason we alsowrite the afterFind() method to keep the old tags in the variable oldTags The methodafterFind() is invoked automatically by Yii when an AR record is populated with thedata from database

We are not going to give details of the TagupdateFrequency() method here Interestedreaders may refer to the file wwwrootyiidemosblogprotectedmodelsTagphp

33 Displaying Posts

In our blog application a post may be displayed among a list of posts or by itself Theformer is implemented as the index operation while the latter the view operation In thissection we customize both operations to fulfill our initial requirements

331 Customizing view Operation

The view operation is implemented by the actionView() method in PostController Itsdisplay is generated by the view view with the view file wwwrootblogprotectedviews

postviewphp

Below is the relevant code implementing the view operation in PostController

public function actionView()

26 3 Post Management

$this-gtrender(rsquoviewrsquoarray(

$post=$this-gtloadModel()

$this-gtrender(rsquoviewrsquoarray(

rsquomodelrsquo=gt$post

))

))

private $ model

public function loadModel()

if($this-gt model===null)

if(isset($ GET[rsquoidrsquo]))

if(Yiiapp()-gtuser-gtisGuest)

$condition=rsquostatus=rsquoPostSTATUS PUBLISHED

rsquo OR status=rsquoPOSTSTATUS ARCHIVED

else

$condition=rsquorsquo

$this-gt model=Postmodel()-gtfindbyPk($ GET[rsquoidrsquo] $condition)

if($this-gt model===null)

throw new CHttpException(404rsquoThe requested page does not existrsquo)

return $this-gt model

Our change mainly lies in the loadModel() method In this method we query the Post

table according to the id GET parameter If the post is not found or if it is not publishedor archived (when the user is a guest) we will throw a 404 HTTP error Otherwise thepost object is returned to actionView() which in turn passes the post object to the viewscript for further display

Tip Yii captures HTTP exceptions (instances of CHttpException) and displaysthem in either predefined templates or customized error views The skeleton appli-cation generated by yiic already contains a customized error view in wwwroot

blogprotectedviewssiteerrorphp We can modify this file if we want tofurther customize the error display

The change in the view script is mainly about ajdusting the formatting and styles of thepost display We will not go into details here Interested readers may refer to wwwroot

blogprotectedviewspostviewphp

33 Displaying Posts 27

332 Customizing index Operation

Like the view operation we customize the index operation in two places the actionIndex()

method in PostController and the view file wwwrootblogprotectedviewspostindex

php We mainly need to add the support for displaying a list of posts that are associatedwith a specified tag

Below is the modified actionIndex() method in PostController

public function actionIndex()

$criteria=new CDbCriteria(array(

rsquoconditionrsquo=gtrsquostatus=rsquoPostSTATUS PUBLISHED

rsquoorderrsquo=gtrsquoupdate time DESCrsquo

rsquowithrsquo=gtrsquocommentCountrsquo

))

if(isset($ GET[rsquotagrsquo]))

$criteria-gtaddSearchCondition(rsquotagsrsquo$ GET[rsquotagrsquo])

$dataProvider=new CActiveDataProvider(rsquoPostrsquo array(

rsquopaginationrsquo=gtarray(

rsquopageSizersquo=gtYiiapp()-gtparams[rsquopostsPerPagersquo]

)

rsquocriteriarsquo=gt$criteria

))

$this-gtrender(rsquoindexrsquoarray(

rsquodataProviderrsquo=gt$dataProvider

))

In the above we first create a query criteria for retrieving post list The criteria statesthat only published posts should be returned and they should be sorted according to theirupdate time in descending order Because when displaying a post in the list we want toshow how many comments the post has received in the criteria we also specify to bringback commentCount which if you remember is a relation declared in Postrelations()

In case when a user wants to see posts with a specific tag we would add a search conditionto the criteria to look for the specified tag

Using the query criteria we create a data provider which mainly serves for three purposesFirst it does pagination of the data when too many results may be returned Herewe customize the pagination by setting the page size to be an application parameterpostsPerPage Second it does sorting according to the user request And finally it feedsthe paginated and sorted data to widgets or view code for presentation

28 3 Post Management

After we finish with actionIndex() we modify the index view as follows Our change ismainly about adding the h1 header when the user specifies to display posts with a tag

ltphp if(empty($ GET[rsquotagrsquo])) gt

lth1gtPosts Tagged with ltigtltphp echo CHtmlencode($ GET[rsquotagrsquo]) gtltigtlth1gt

ltphp endif gt

ltphp $this-gtwidget(rsquoziiwidgetsCListViewrsquo array(

rsquodataProviderrsquo=gt$dataProvider

rsquoitemViewrsquo=gtrsquo viewrsquo

rsquotemplatersquo=gtitemsnpager)) gt

Note that in the above we use [CListView] to display the post list This widget requires apartial view to display the detail of each individual post Here we specify the partial viewto be view which means the file wwwrootblogprotectedviewspost viewphp In thisview script we can acccess the post instance being displayed via a local variable named$data

34 Managing Posts

Managing posts mainly refers to listing posts in an administrative view that allows us tosee posts with all statuses updating them and deleting them They are accomplished bythe admin operation and the delete operation respectively The code generated by yiic

does not need much modification Below we mainly explain how these two operations areimplemented

341 Listing Posts in Tabular View

The admin operation shows posts with all statuses in a tabular view The view supportssorting and pagination The following is the actionAdmin() method in PostController

public function actionAdmin()

$dataProvider=new CActiveDataProvider(rsquoPostrsquo array(

rsquosortrsquo=gtarray(

rsquodefaultOrderrsquo=gtrsquostatus update time DESCrsquo

)

))

$this-gtrender(rsquoadminrsquoarray(

rsquodataProviderrsquo=gt$dataProvider

))

34 Managing Posts 29

The above code is very similar to that in actionIndex() The main difference is in thequery criteria Here we do not specify any particular query criteria since we want to seeall posts We do customize the sort property of the data provider so that by default theposts are shown in the order of their statuses and update time

Below is the code for the admin view

lth1gtManage Postslth1gt

ltphp $this-gtwidget(rsquoziiwidgetsgridCGridViewrsquo array(

rsquodataProviderrsquo=gt$dataProvider

rsquocolumnsrsquo=gtarray(

array(

rsquonamersquo=gtrsquotitlersquo

rsquotypersquo=gtrsquorawrsquo

rsquovaluersquo=gtrsquoCHtmllink(CHtmlencode($data-gttitle) $data-gturl)rsquo

)

array(

rsquonamersquo=gtrsquostatusrsquo

rsquovaluersquo=gtrsquoLookupitem(PostStatus$data-gtstatus)rsquo

)

rsquocreate timedatetimersquo

rsquoupdate timedatetimersquo

array(

rsquoclassrsquo=gtrsquoCButtonColumnrsquo

)

)

)) gt

We use [CGridView] to display the posts It allows us to sort by a column and paginatethrough the posts if there are too many to be displayed in a single page Our change ismainly about how to display each column For example for the title column we specifythat it should be displayed as a hyperlink that points to the detailed view of the post

Tip When displaying text we call CHtmlencode() to encode HTML entities init This prevents from cross-site scripting attack

342 Deleting Posts

In the admin data grid there is a delete button in each row Clicking on the button shoulddelete the corresponding post Internally this triggers the delete action implemented asfollows

30 3 Post Management

public function actionDelete()

if(Yiiapp()-gtrequest-gtisPostRequest)

we only allow deletion via POST request

$this-gtloadModel()-gtdelete()

if(isset($ POST[rsquoajaxrsquo]))

$this-gtredirect(array(rsquoindexrsquo))

else

throw new CHttpException(400rsquoInvalid request Please do not repeat this request againrsquo)

The above code is the one generated by the yiic tool without any change We wouldlike to explain a little bit more about the checking on $ POST[rsquoajaxrsquo] The [CGridView]widget has a very nice feature that its sorting pagination and deletion operations are alldone in AJAX mode by default That means the whole page does not get reloaded if anyof the above operations is performed However it is also possible that the widget runs innon-AJAX mode (by setting its ajaxUpdate property to be false or disabling JavaScript onthe client side) It is necessary for the delete action to differentiate these two scenarios ifthe delete request is made via AJAX we should not redirect the user browser otherwisewe should

Deleting a post should also cause the deletion of all comments for that post In additionwe should also update the tbl tag table regarding the tags for the deleted post Both ofthese tasks can be achieved by writing an afterDelete method in the Post model class asfollows

protected function afterDelete()

parentafterDelete()

Commentmodel()-gtdeleteAll(rsquopost id=rsquo$this-gtid)

Tagmodel()-gtupdateFrequency($this-gttags rsquorsquo)

The above code is very straightforward it first deletes all those comments whose post id

is the same as the ID of the deleted post it then updates the tbl tag table for the tags

of the deleted post

34 Managing Posts 31

Tip We have to explicitly delete all comments for the deleted post here becauseSQLite does not really support foreign key constraints In a DBMS that supportsthis constraint (such as MySQL PostgreSQL) the foreign key constraint can be setup such that the DBMS automatically deletes the related comments if the post isdeleted In that case we no longer this explicit deletion call in our code

32 3 Post Management

Chapter 4

Comment Management

41 Customizing Comment Model

For the Comment model we mainly need to customize the rules() and attributeLabels()

methods The attributeLabels() method returns a mapping between attribute namesand attribute labels We do not need to touch relations() since the code generated bythe yiic tool is good enough

411 Customizing rules() Method

We first customize the validation rules generated by the yiic tool The following rules areused for comments

public function rules()

return array(

array(rsquocontent author emailrsquo rsquorequiredrsquo)

array(rsquoauthor email urlrsquo rsquolengthrsquo rsquomaxrsquo=gt128)

array(rsquoemailrsquorsquoemailrsquo)

array(rsquourlrsquorsquourlrsquo)

)

In the above we specify that the author email and content attributes are required thelength of author email and url cannot exceed 128 the email attribute must be a validemail address and the url attribute must be a valid URL

412 Customizing attributeLabels() Method

We then customize the attributeLabels() method to declare the label display for eachmodel attribute This method returns an array consisting of name-label pairs When wecall CHtmlactiveLabel() to display an attribute label

34 4 Comment Management

public function attributeLabels()

return array(

rsquoidrsquo =gt rsquoIdrsquo

rsquocontentrsquo =gt rsquoCommentrsquo

rsquostatusrsquo =gt rsquoStatusrsquo

rsquocreate timersquo =gt rsquoCreate Timersquo

rsquoauthorrsquo =gt rsquoNamersquo

rsquoemailrsquo =gt rsquoEmailrsquo

rsquourlrsquo =gt rsquoWebsitersquo

rsquopost idrsquo =gt rsquoPostrsquo

)

Tip If the label for an attribute is not declared in attributeLabels() an algo-rithm will be used to generate an appropriate label For example a label VerifyCode will be generated for attributes verify code or verifyCode

413 Customizing Saving Process

Because we want to record the creation time of a comment we override the beforeSave()

method of Comment like we do for the Post model

protected function beforeSave()

if(parentbeforeSave())

if($this-gtisNewRecord)

$this-gtcreate time=time()

return true

else

return false

42 Creating and Displaying Comments

In this section we implement the comment display and creation features

421 Displaying Comments

Instead of displaying and creating comments on individual pages we use the post detailpage (generated by the view action of PostController) Below the post content display

42 Creating and Displaying Comments 35

we display first a list of comments belonging to that post and then a comment creationform

In order to display comments on the post detail page we modify the view script wwwrootblogprotectedviewspostviewphp as follows

ltdiv id=commentsgt

ltphp if($model-gtcommentCountgt=1) gt

lth3gt

ltphp echo $model-gtcommentCount rsquocomment(s)rsquo gt

lth3gt

ltphp $this-gtrenderPartial(rsquo commentsrsquoarray(

rsquopostrsquo=gt$model

rsquocommentsrsquo=gt$model-gtcomments

)) gt

ltphp endif gt

ltdivgt

In the above we call renderPartial() to render a partial view named comments to displaythe list of comments belonging to the current post Note that in the view we use theexpression $model-gtcomments to retrieve the comments for the post This is valid becausewe have declared a comments relation in the Post class Evaluating this expression wouldtrigger an implicit JOIN database query to bring back the proper comments This featureis known as lazy relational query

The partial view comments is not very interesting It mainly goes through every commentand displays the detail of it Interested readers may refer to wwwrootyiidemosblog

protectedpost commentsphp

422 Creating Comments

To handle comment creation we first modify the actionView() method of PostController

as follows

public function actionView()

$post=$this-gtloadModel()

$comment=$this-gtnewComment($post)

$this-gtrender(rsquoviewrsquoarray(

rsquomodelrsquo=gt$post

rsquocommentrsquo=gt$comment

))

36 4 Comment Management

protected function newComment($post)

$comment=new Comment

if(isset($ POST[rsquoCommentrsquo]))

$comment-gtattributes=$ POST[rsquoCommentrsquo]

if($post-gtaddComment($comment))

if($comment-gtstatus==CommentSTATUS PENDING)

Yiiapp()-gtuser-gtsetFlash(rsquocommentSubmittedrsquorsquoThank yoursquo)

$this-gtrefresh()

return $comment

In the above we call the newComment() method before we render view In the newComment()

method we generate a Comment instance and check if the comment form is submitted If sowe try to add the comment for the post by calling $post-gtaddComment($comment) If it goesthrough we refresh the post detail page In case the comment needs to be approved we willshow a flash message to indicate this decision A flash message is usually a confirmationmessage displayed to end users If the user clicks on the refresh button of his browser themessage will disappear

We also need to modify wwwrootblogprotectedviewspostviewphp furthermore

ltdiv id=commentsgt

lth3gtLeave a Commentlth3gt

ltphp if(Yiiapp()-gtuser-gthasFlash(rsquocommentSubmittedrsquo)) gt

ltdiv class=successgt

ltphp echo Yiiapp()-gtuser-gtgetFlash(rsquocommentSubmittedrsquo) gt

ltdivgt

ltphp else gt

ltphp $this-gtrenderPartial(rsquocomment formrsquoarray(

rsquomodelrsquo=gt$comment

)) gt

ltphp endif gt

ltdivgtlt-- comments --gt

43 Managing Comments 37

In the above code we display the flash message if it is available If not we displaythe comment input form by rendering the partial view wwwrootblogprotectedviews

comment formphp

43 Managing Comments

Comment management includes updating deleting and approving comments These op-erations are implemented as actions in the CommentController class

431 Updating and Deleting Comments

The code generated by yiic for updating and deleting comments remains largely un-changed

432 Approving Comments

When comments are newly created they are in pending approval status and need to beapproved in order to be visible to guest users Approving a comment is mainly aboutchanging the status column of the comment

We create an actionApprove() method in CommentController as follows

public function actionApprove()

if(Yiiapp()-gtrequest-gtisPostRequest)

$comment=$this-gtloadModel()

$comment-gtapprove()

$this-gtredirect(array(rsquoindexrsquo))

else

throw new CHttpException(400rsquoInvalid requestrsquo)

In the above when the approve action is invoked via a POST request we call the approve()

method defined in the Comment model to change the status We then redirect the userbrowser to the page displaying the post that this comment belongs to

We also modify the actionIndex() method of Comment to show all comments We wouldlike to see comments pending approved to show up first

public function actionIndex()

38 4 Comment Management

$dataProvider=new CActiveDataProvider(rsquoCommentrsquo array(

rsquocriteriarsquo=gtarray(

rsquowithrsquo=gtrsquopostrsquo

rsquoorderrsquo=gtrsquotstatus tcreate time DESCrsquo

)

))

$this-gtrender(rsquoindexrsquoarray(

rsquodataProviderrsquo=gt$dataProvider

))

Notice that in the above code because both tbl post and tbl comment have columns statusand create time we need to disambiguate the corresponding column reference by prefixingthem with table alias names As described in the guide the alias for the primary table ina relational query is always t Therefore we are prefixing t to the status and create time

columns in the above code

Like the post index view the index view for CommentController uses [CListView] to displaythe comment list which in turn uses the partial view wwwrootblogprotectedviews

comment viewphp to display the detail of each individual comment We will not go intodetails here Interested readers may refer to the corresponding file in the blog demowwwrootyiidemosblogprotectedviewscomment viewphp

Chapter 5

Portlets

51 Customizing Page Layout

Features like rdquothe most recent commentsrdquo rdquotag cloudrdquo are better to be implemented inportlets A portlet is a pluggable user interface component that renders a fragment ofHTML code In this section we describe how to set up the portlet architecture for ourblog application

Before we go into details about developing portlets we first need to adjust our page layoutso that the portlets can be placed in a side bar

A page layout determines how to share common sections (eg header footer) amongdifferent pages and how to embed contents that are specific for different pages In Yiia page layout is represented as a view under the protectedviewslayouts folder Whenwe call the render() method in a controller the rendering result will be automaticallyembedded into this layout view before it is displayed to end users The default layoutview is named as main which means the wwwrootblogprotectedviewslayoutsmain

php file in our blog application

We could directly modify the main layout to add a side bar However doing so wouldmake some irrelevant pages such as about contact pages to have a side bar which is notalways desirable For this reason we need different layouts for different pages

We thus create two different layouts column1 and column2 The former displays the pagein a single column while the latter displays two columns in the page one of which is theside bar We will use column2 layout for the post and comment pages and column1 forthe rest of the pages Because both column1 and column2 still share the same header andfooter sections we let them to share the main layout

Based on the above description we create the column1 layout as follows

ltphp $this-gtbeginContent(rsquolayoutsmainrsquo) gt

ltdiv class=containergt

40 5 Portlets

ltdiv id=contentgt

ltphp echo $content gt

ltdivgtlt-- content --gt

ltdivgt

ltphp $this-gtendContent() gt

and the column2 layout as follows

ltphp $this-gtbeginContent(rsquolayoutsmainrsquo) gt

ltdiv class=containergt

ltdiv class=span-18gt

ltdiv id=contentgt

ltphp echo $content gt

ltdivgtlt-- content --gt

ltdivgt

ltdiv class=span-6 lastgt

ltdiv id=sidebargt

portlets to be placed here

ltdivgtlt-- sidebar --gt

ltdivgt

ltdivgt

ltphp $this-gtendContent() gt

As we can see both column1 and column2 are enclosed in a pair of beginContent andendContent calls They essentially use the specified view layoutsmain to decorate theenclosed content In all these layout views the variable $content refers to the dynamiccontent to be embedded into the layout

Besides customizing the layout view file we also need to adjust the CSS file wwwroot

blogcssmaincss so that the overall appearance would look like what we see in the blogdemo We will not go into details here

Info The skeleton application generated by the yiic tool uses the BluePrint CSSframework It provides a nice font reset and a robust grid system For more detailson how to use this framework please refer to its documentation

After creating the new layouts we need to set the layout property of controllers to makeuse of them So far we have three controllers SiteController PostController andCommentController We set SiteController to use column1 as the default layout whilethe rest two of the controllers use column2 for example

class PostController extends Controller

52 Creating User Menu Portlet 41

public $layout=rsquocolumn2rsquo

Tip If some view of the controller does not use the same layout as declaredby the layout property it may customize it in the view by a simple assignment$this-gtlayout=rsquonewlayoutrsquo

52 Creating User Menu Portlet

Based on the requirements analysis we need three different portlets the rdquouser menurdquoportlet the rdquotag cloudrdquo portlet and the rdquorecent commentsrdquo portlet We will develop theseportlets by extending the [CPortlet] widget provided by Yii

In this section we will develop our first concrete portlet - the user menu portlet whichdisplays a list of menu items that are only available to authenticated users The menucontains four items

bull Approve Comments a hyperlink that leads to a list of comments pending approval

bull Create New Post a hyperlink that leads to the post creation page

bull Manage Posts a hyperlink that leads to the post management page

bull Logout a link button that would log out the current user

521 Creating UserMenu Class

We create the UserMenu class to represent the logic part of the user menu portlet Theclass is saved in the file wwwrootblogprotectedcomponentsUserMenuphp which has thefollowing content

Yiiimport(rsquoziiwidgetsCPortletrsquo)

class UserMenu extends CPortlet

public function init()

$this-gttitle=CHtmlencode(Yiiapp()-gtuser-gtname)

parentinit()

42 5 Portlets

protected function renderContent()

$this-gtrender(rsquouserMenursquo)

The UserMenu class extends from the CPortlet class that we created previously It overridesboth the init() method and the renderContent() method of CPortlet The former setsthe portlet title to be the name of the current user the latter generates the portlet bodycontent by rendering a view named userMenu

Tip Notice that we have to explicitly include the CPortlet class by calling Yii

import() before we refer to it the first time This is because CPortlet is part of thezii project ndash the official extension library for Yii For performance considerationclasses in this project are not listed as core classes Therefore we have to import itbefore we use it the first time

522 Creating userMenu View

Next we create the userMenu view which is saved in the file wwwrootblogprotected

componentsviewsuserMenuphp

ltulgt

ltligtltphp echo CHtmllink(rsquoCreate New Postrsquoarray(rsquopostcreatersquo)) gtltligt

ltligtltphp echo CHtmllink(rsquoManage Postsrsquoarray(rsquopostadminrsquo)) gtltligt

ltligtltphp echo CHtmllink(rsquoApprove Commentsrsquoarray(rsquocommentindexrsquo))

rsquo (rsquo Commentmodel()-gtpendingCommentCount rsquo)rsquo gtltligt

ltligtltphp echo CHtmllink(rsquoLogoutrsquoarray(rsquositelogoutrsquo)) gtltligt

ltulgt

Info By default view files for a widget should be placed under the views sub-directory of the directory containing the widget class file The file name must bethe same as the view name

523 Using UserMenu Portlet

It is time for us to make use of our newly completed UserMenu portlet We modify thelayout view file wwwrootblogprotectedviewslayoutscolumn2php as follows

53 Creating Tag Cloud Portlet 43

ltdiv id=sidebargt

ltphp if(Yiiapp()-gtuser-gtisGuest) $this-gtwidget(rsquoUserMenursquo) gt

ltdivgt

In the above we call the widget() method to generate and execute an instance of theUserMenu class Because the portlet should only be displayed to authenticated users weonly call widget() when the isGuest property of the current user is false (meaning theuser is authenticated)

524 Testing UserMenu Portlet

Letrsquos test what we have so far

1 Open a browser window and enter the URL httpwwwexamplecomblogindex

php Verify that there is nothing displayed in the side bar section of the page

2 Click on the Login hyperlink and fill out the login form to login If successful verifythat the UserMenu portlet appears in the side bar and the portlet has the usernameas its title

3 Click on the rsquoLogoutrsquo hyperlink in the UserMenu portlet Verify that the logout actionis successful and the UserMenu portlet disappears

525 Summary

What we have created is a portlet that is highly reusable We can easily reuse it in adifferent project with little or no modification Moreover the design of this portlet followsclosely the philosophy that logic and presentation should be separated While we did notpoint this out in the previous sections such practice is used nearly everywhere in a typicalYii application

53 Creating Tag Cloud Portlet

Tag cloud displays a list of post tags with visual decorations hinting the popularity of eachindividual tag

531 Creating TagCloud Class

We create the TagCloud class in the file wwwrootblogprotectedcomponentsTagCloud

php The file has the following content

44 5 Portlets

Yiiimport(rsquoziiwidgetsCPortletrsquo)

class TagCloud extends CPortlet

public $title=rsquoTagsrsquo

public $maxTags=20

protected function renderContent()

$tags=Tagmodel()-gtfindTagWeights($this-gtmaxTags)

foreach($tags as $tag=gt$weight)

$link=CHtmllink(CHtmlencode($tag) array(rsquopostindexrsquorsquotagrsquo=gt$tag))

echo CHtmltag(rsquospanrsquo array(

rsquoclassrsquo=gtrsquotagrsquo

rsquostylersquo=gtfont-size$weightpt) $link)n

Unlike the UserMenu portlet the TagCloud portlet does not use a view Instead its presen-tation is done in the renderContent() method This is because the presentation does notcontain much HTML tags

We display each tag as a hyperlink to the post index page with the corresponding tagparameter The font size of each tag link is adjusted according to their relative weightamong other tags If a tag has higher frequency value than the other it will have a biggerfont size

532 Using TagCloud Portlet

Usage of the TagCloud portlet is very simple We modify the layout file wwwrootblog

protectedviewslayoutsmainphp as follows

ltdiv id=sidebargt

ltphp if(Yiiapp()-gtuser-gtisGuest) $this-gtwidget(rsquoUserMenursquo) gt

ltphp $this-gtwidget(rsquoTagCloudrsquo array(

rsquomaxTagsrsquo=gtYiiapp()-gtparams[rsquotagCloudCountrsquo]

)) gt

ltdivgt

54 Creating Recent Comments Portlet 45

54 Creating Recent Comments Portlet

In this section we create the last portlet that displays a list of comments recently pub-lished

541 Creating RecentComments Class

We create the RecentComments class in the file wwwrootblogprotectedcomponentsRecentCommentsphp The file has the following content

Yiiimport(rsquoziiwidgetsCPortletrsquo)

class RecentComments extends CPortlet

public $title=rsquoRecent Commentsrsquo

public $maxComments=10

public function getRecentComments()

return Commentmodel()-gtfindRecentComments($this-gtmaxComments)

protected function renderContent()

$this-gtrender(rsquorecentCommentsrsquo)

In the above we invoke the findRecentComments method which is defined in the Comment

class as follows

class Comment extends CActiveRecord

public function findRecentComments($limit=10)

return $this-gtwith(rsquopostrsquo)-gtfindAll(array(

rsquoconditionrsquo=gtrsquotstatus=rsquoselfSTATUS APPROVED

rsquoorderrsquo=gtrsquotcreate time DESCrsquo

rsquolimitrsquo=gt$limit

))

46 5 Portlets

542 Creating recentComments View

The recentComments view is saved in the file wwwrootblogprotectedcomponentsviews

recentCommentsphp It simply displays every comment returned by the RecentComments

getRecentComments() method

543 Using RecentComments Portlet

We modify the layout file wwwrootblogprotectedviewslayoutsmainphp to embedthis last portlet

ltdiv id=sidebargt

ltphp if(Yiiapp()-gtuser-gtisGuest) $this-gtwidget(rsquoUserMenursquo) gt

ltphp $this-gtwidget(rsquoTagCloudrsquo array(

rsquomaxTagsrsquo=gtYiiapp()-gtparams[rsquotagCloudCountrsquo]

)) gt

ltphp $this-gtwidget(rsquoRecentCommentsrsquo array(

rsquomaxCommentsrsquo=gtYiiapp()-gtparams[rsquorecentCommentCountrsquo]

)) gt

ltdivgt

Chapter 6

Final Work

61 Beautifying URLs

The URLs linking various pages of our blog application currently look ugly For examplethe URL for the page showing a post looks like the following

indexphpr=postshowampid=1

In this section we describe how to beautify these URLs and make them SEO-friendlyOur goal is to be able to use the following URLs in the application

1 indexphppostsyii leads to the page showing a list of posts with tag yii

2 indexphppost2A+Test+Post leads to the page showing the detail of the postwith ID 2 whose title is A Test Post

3 indexphppostupdate1 leads to the page that allows updating the post with ID1

Note that in the second URL format we include the post title in the URL This is mainlyto make the URL SEO friendly It is said that search engines may also respect the wordsfound in a URL when it is being indexed

To achieve our goal we modify the application configuration as follows

return array(

rsquocomponentsrsquo=gtarray(

rsquourlManagerrsquo=gtarray(

rsquourlFormatrsquo=gtrsquopathrsquo

rsquorulesrsquo=gtarray(

48 6 Final Work

rsquopostltidd+gtlttitlegtrsquo=gtrsquopostviewrsquorsquopostslttaggtrsquo=gtrsquopostindexrsquo

)

)

)

)

In the above we configure the urlManager component by setting its urlFormat propertyto be path and adding a set of rules

The rules are used by urlManager to parse and create the URLs in the desired formatFor example the first rule says that if a URL indexphppostsyii is requested theurlManager component should be responsible to dispatch the request to the route post

index and generate a tag GET parameter with the value yii On the other hand whencreating a URL with the route postindex and parameter tag the urlManager componentwill also use this rule to generate the desired URL indexphppostsyii For this reasonwe say that urlManager is a two-way URL manager

The urlManager component can further beautify our URLs such as hiding indexphp inthe URLs appending suffix like html to the URLs We can obtain these features easilyby configuring various properties of urlManager in the application configuration For moredetails please refer to the Guide

62 Logging Errors

A production Web application often needs sophisticated logging for various events In ourblog application we would like to log the errors occurring when it is being used Sucherrors could be programming mistakes or usersrsquo misuse of the system Logging these errorswill help us to improve the blog application

We enable the error logging by modifying the application configuration as follows

return array(

rsquopreloadrsquo=gtarray(rsquologrsquo)

rsquocomponentsrsquo=gtarray(

rsquologrsquo=gtarray(

rsquoclassrsquo=gtrsquoCLogRouterrsquo

rsquoroutesrsquo=gtarray(

array(

rsquoclassrsquo=gtrsquoCFileLogRoutersquo

rsquolevelsrsquo=gtrsquoerror warningrsquo

63 Final Tune-up and Deployment 49

)

)

)

)

)

With the above configuration if an error or warning occurs detailed information will belogged and saved in a file located under the directory wwwrootblogprotectedruntime

The log component offers more advanced features such as sending log messages to a listof email addresses displaying log messages in JavaScript console window etc For moredetails please refer to the Guide

63 Final Tune-up and Deployment

We are close to finish our blog application Before deployment we would like to do sometune-ups

631 Changing Home Page

We change to use the post list page as the home page We modify the application config-uration as follows

return array(

rsquodefaultControllerrsquo=gtrsquopostrsquo

)

Tip Because PostController already declares list to be its default action whenwe access the home page of the application we will see the result generated by thelist action of the post controller

632 Enabling Schema Caching

Because ActiveRecord relies on the metadata about tables to determine the column infor-mation it takes time to read the metadata and analyze it This may not be a problemduring development stage but for an application running in production mode it is a totalwaste of time if the database schema does not change Therefore we should enable theschema caching by modifying the application configuration as follows

50 6 Final Work

return array(

rsquocomponentsrsquo=gtarray(

rsquocachersquo=gtarray(

rsquoclassrsquo=gtrsquoCDbCachersquo

)

rsquodbrsquo=gtarray(

rsquoclassrsquo=gtrsquosystemdbCDbConnectionrsquo

rsquoconnectionStringrsquo=gtrsquosqlitewwwrootblogprotecteddatablogdbrsquo

rsquoschemaCachingDurationrsquo=gt3600

)

)

)

In the above we first add a cache component which uses a default SQLite database as thecaching storage If our server is equipped with other caching extensions such as APCwe could change to use them as well We also modify the db component by setting itsschemaCachingDuration property to be 3600 which means the parsed database schemadata can remain valid in cache for 3600 seconds

633 Disabling Debugging Mode

We modify the entry script file wwwrootblogindexphp by removing the line definingthe constant YII DEBUG This constant is useful during development stage because it allowsYii to display more debugging information when an error occurs However when theapplication is running in production mode displaying debugging information is not agood idea because it may contain sensitive information such as where the script file islocated and the content in the file etc

634 Deploying the Application

The final deployment process manly involves copying the directory wwwrootblog to thetarget directory The following checklist shows every needed step

1 Install Yii in the target place if it is not available

2 Copy the entire directory wwwrootblog to the target place

3 Edit the entry script file indexphp by pointing the $yii variable to the new Yiibootstrap file

4 Edit the file protectedyiicphp by setting the $yiic variable to be the new Yiiyiicphp file

64 Future Enhancements 51

5 Change the permission of the directories assets and protectedruntime so that theyare writable by the Web server process

64 Future Enhancements

641 Using a Theme

Without writing any code our blog application is already themeable To use a themewe mainly need to develop the theme by writing customized view files in the theme Forexample to use a theme named classic that uses a different page layout we would createa layout view file wwwrootblogthemesclassicviewslayoutsmainphp We also needto change the application configuration to indicate our choice of the classic theme

return array(

rsquothemersquo=gtrsquoclassicrsquo

)

642 Internationalization

We may also internationalize our blog application so that its pages can be displayed indifferent languages This mainly involves efforts in two aspects

First we may create view files in different languages For example for the list pageof PostController we can create a view file wwwrootblogprotectedviewspostzh cn

listphp When the application is configured to use simplified Chinese (the language codeis zh cn) Yii will automatically use this new view file instead of the original one

Second we may create message translations for those messages generated by code Themessage translations should be saved as files under the directory wwwrootblogprotected

messages We also need to modify the code where we use text strings by enclosing themin the method call Yiit()

For more details about internationalization please refer to the Guide

643 Improving Performance with Cache

While the Yii framework itself is very efficient it is not necessarily true that an applicationwritten in Yii efficient There are several places in our blog application that we can improvethe performance For example the tag clould portlet could be one of the performancebottlenecks because it involves complex database query and PHP logic

52 6 Final Work

We can make use of the sophisticated caching feature provided by Yii to improve theperformance One of the most useful components in Yii is COutputCache which caches afragment of page display so that the underlying code generating the fragment does not needto be executed for every request For example in the layout file wwwrootblogprotected

viewslayoutsmainphp we can enclose the tag cloud portlet with COutputCache

ltphp if($this-gtbeginCache(rsquotagCloudrsquo array(rsquodurationrsquo=gt3600))) gt

ltphp $this-gtwidget(rsquoTagCloudrsquo array(

rsquomaxTagsrsquo=gtYiiapp()-gtparams[rsquotagCloudCountrsquo]

)) gt

ltphp $this-gtendCache() gt

With the above code the tag cloud display will be served from cache instead of beinggenerated on-the-fly for every request The cached content will remain valid in cache for3600 seconds

644 Adding New Features

Our blog application only has very basic functionalities To become a complete blogsystem more features are needed for example calendar portlet email notifications postcategorization archived post portlet and so on We will leave the implementation of thesefeatures to interested readers

  • Contents
  • License
  • Getting Started
    • Building a Blog System using Yii
    • Testdriving with Yii
      • Installing Yii
      • Creating Skeleton Application
      • Application Workflow
        • Requirements Analysis
        • Overall Design
          • Initial Prototyping
            • Setting Up Database
              • Creating Database
              • Establishing Database Connection
                • Scaffolding
                • Authenticating User
                • Summary
                  • Post Management
                    • Customizing Post Model
                      • Customizing rules() Method
                      • Customizing relations() Method
                      • Representing Status in Text
                        • Creating and Updating Posts
                          • Customizing Access Control
                          • Customizing create and update Operations
                            • Displaying Posts
                              • Customizing view Operation
                              • Customizing index Operation
                                • Managing Posts
                                  • Listing Posts in Tabular View
                                  • Deleting Posts
                                      • Comment Management
                                        • Customizing Comment Model
                                          • Customizing rules() Method
                                          • Customizing attributeLabels() Method
                                          • Customizing Saving Process
                                            • Creating and Displaying Comments
                                              • Displaying Comments
                                              • Creating Comments
                                                • Managing Comments
                                                  • Updating and Deleting Comments
                                                  • Approving Comments
                                                      • Portlets
                                                        • Customizing Page Layout
                                                        • Creating User Menu Portlet
                                                          • Creating UserMenu Class
                                                          • Creating userMenu View
                                                          • Using UserMenu Portlet
                                                          • Testing UserMenu Portlet
                                                          • Summary
                                                            • Creating Tag Cloud Portlet
                                                              • Creating TagCloud Class
                                                              • Using TagCloud Portlet
                                                                • Creating Recent Comments Portlet
                                                                  • Creating RecentComments Class
                                                                  • Creating recentComments View
                                                                  • Using RecentComments Portlet
                                                                      • Final Work
                                                                        • Beautifying URLs
                                                                        • Logging Errors
                                                                        • Final Tune-up and Deployment
                                                                          • Changing Home Page
                                                                          • Enabling Schema Caching
                                                                          • Disabling Debugging Mode
                                                                          • Deploying the Application
                                                                            • Future Enhancements
                                                                              • Using a Theme
                                                                              • Internationalization
                                                                              • Improving Performance with Cache
                                                                              • Adding New Features
Page 31: Yii Blog 1.1.0 Demo Tutor

24 3 Post Management

access in any other scenario Note that these rules are evaluated in the order they arelisted here The first rule matching the current context makes the access decision Forexample if the current user is the system owner who tries to visit the post creation pagethe second rule will match and it will give the access to the user

322 Customizing create and update Operations

The create and update operations are very similar They both need to display an HTMLform to collect user inputs validate them and save them into database The main differ-ence is that the update operation will pre-populate the form with the existing post datafound in the database For this reason the yiic tool generates a partial view wwwroot

blogprotectedviewspost formphp that is embedded in both the create and update

views to render the needed HTML form

We first change the formphp file so that the HTML form only collects the inputs wewant title content tags and status We use plain text fields to collect inputs for thefirst three attributes and a dropdown list to collect input for status The dropdown listoptions are the text displays of the possible post statuses

ltphp echo CHtmlactiveDropDownList($postrsquostatusrsquoLookupitems(rsquoPostStatusrsquo)) gt

In the above we call Lookupitems(rsquoPostStatusrsquo) to bring back the list of post statuses

We then modify the Post class so that it can automatically set some attributes (eg create

time author id) before a post is saved to the database We override the beforeSave()

method as follows

protected function beforeSave()

if(parentbeforeSave())

if($this-gtisNewRecord)

$this-gtcreate time=$this-gtupdate time=time()

$this-gtauthor id=Yiiapp()-gtuser-gtid

else

$this-gtupdate time=time()

return true

else

return false

33 Displaying Posts 25

When we save a post we want to update the tbl tag table to reflect the change of tagfrequencies We can do this work in the afterSave() method which is automaticallyinvoked by Yii after a post is successfully saved into the database

protected function afterSave()

parentafterSave()

Tagmodel()-gtupdateFrequency($this-gt oldTags $this-gttags)

private $ oldTags

protected function afterFind()

parentafterFind()

$this-gt oldTags=$this-gttags

In the implementation because we want to detect if the user changes the tags in case he isupdating an existing post we need to know what the old tags are For this reason we alsowrite the afterFind() method to keep the old tags in the variable oldTags The methodafterFind() is invoked automatically by Yii when an AR record is populated with thedata from database

We are not going to give details of the TagupdateFrequency() method here Interestedreaders may refer to the file wwwrootyiidemosblogprotectedmodelsTagphp

33 Displaying Posts

In our blog application a post may be displayed among a list of posts or by itself Theformer is implemented as the index operation while the latter the view operation In thissection we customize both operations to fulfill our initial requirements

331 Customizing view Operation

The view operation is implemented by the actionView() method in PostController Itsdisplay is generated by the view view with the view file wwwrootblogprotectedviews

postviewphp

Below is the relevant code implementing the view operation in PostController

public function actionView()

26 3 Post Management

$this-gtrender(rsquoviewrsquoarray(

$post=$this-gtloadModel()

$this-gtrender(rsquoviewrsquoarray(

rsquomodelrsquo=gt$post

))

))

private $ model

public function loadModel()

if($this-gt model===null)

if(isset($ GET[rsquoidrsquo]))

if(Yiiapp()-gtuser-gtisGuest)

$condition=rsquostatus=rsquoPostSTATUS PUBLISHED

rsquo OR status=rsquoPOSTSTATUS ARCHIVED

else

$condition=rsquorsquo

$this-gt model=Postmodel()-gtfindbyPk($ GET[rsquoidrsquo] $condition)

if($this-gt model===null)

throw new CHttpException(404rsquoThe requested page does not existrsquo)

return $this-gt model

Our change mainly lies in the loadModel() method In this method we query the Post

table according to the id GET parameter If the post is not found or if it is not publishedor archived (when the user is a guest) we will throw a 404 HTTP error Otherwise thepost object is returned to actionView() which in turn passes the post object to the viewscript for further display

Tip Yii captures HTTP exceptions (instances of CHttpException) and displaysthem in either predefined templates or customized error views The skeleton appli-cation generated by yiic already contains a customized error view in wwwroot

blogprotectedviewssiteerrorphp We can modify this file if we want tofurther customize the error display

The change in the view script is mainly about ajdusting the formatting and styles of thepost display We will not go into details here Interested readers may refer to wwwroot

blogprotectedviewspostviewphp

33 Displaying Posts 27

332 Customizing index Operation

Like the view operation we customize the index operation in two places the actionIndex()

method in PostController and the view file wwwrootblogprotectedviewspostindex

php We mainly need to add the support for displaying a list of posts that are associatedwith a specified tag

Below is the modified actionIndex() method in PostController

public function actionIndex()

$criteria=new CDbCriteria(array(

rsquoconditionrsquo=gtrsquostatus=rsquoPostSTATUS PUBLISHED

rsquoorderrsquo=gtrsquoupdate time DESCrsquo

rsquowithrsquo=gtrsquocommentCountrsquo

))

if(isset($ GET[rsquotagrsquo]))

$criteria-gtaddSearchCondition(rsquotagsrsquo$ GET[rsquotagrsquo])

$dataProvider=new CActiveDataProvider(rsquoPostrsquo array(

rsquopaginationrsquo=gtarray(

rsquopageSizersquo=gtYiiapp()-gtparams[rsquopostsPerPagersquo]

)

rsquocriteriarsquo=gt$criteria

))

$this-gtrender(rsquoindexrsquoarray(

rsquodataProviderrsquo=gt$dataProvider

))

In the above we first create a query criteria for retrieving post list The criteria statesthat only published posts should be returned and they should be sorted according to theirupdate time in descending order Because when displaying a post in the list we want toshow how many comments the post has received in the criteria we also specify to bringback commentCount which if you remember is a relation declared in Postrelations()

In case when a user wants to see posts with a specific tag we would add a search conditionto the criteria to look for the specified tag

Using the query criteria we create a data provider which mainly serves for three purposesFirst it does pagination of the data when too many results may be returned Herewe customize the pagination by setting the page size to be an application parameterpostsPerPage Second it does sorting according to the user request And finally it feedsthe paginated and sorted data to widgets or view code for presentation

28 3 Post Management

After we finish with actionIndex() we modify the index view as follows Our change ismainly about adding the h1 header when the user specifies to display posts with a tag

ltphp if(empty($ GET[rsquotagrsquo])) gt

lth1gtPosts Tagged with ltigtltphp echo CHtmlencode($ GET[rsquotagrsquo]) gtltigtlth1gt

ltphp endif gt

ltphp $this-gtwidget(rsquoziiwidgetsCListViewrsquo array(

rsquodataProviderrsquo=gt$dataProvider

rsquoitemViewrsquo=gtrsquo viewrsquo

rsquotemplatersquo=gtitemsnpager)) gt

Note that in the above we use [CListView] to display the post list This widget requires apartial view to display the detail of each individual post Here we specify the partial viewto be view which means the file wwwrootblogprotectedviewspost viewphp In thisview script we can acccess the post instance being displayed via a local variable named$data

34 Managing Posts

Managing posts mainly refers to listing posts in an administrative view that allows us tosee posts with all statuses updating them and deleting them They are accomplished bythe admin operation and the delete operation respectively The code generated by yiic

does not need much modification Below we mainly explain how these two operations areimplemented

341 Listing Posts in Tabular View

The admin operation shows posts with all statuses in a tabular view The view supportssorting and pagination The following is the actionAdmin() method in PostController

public function actionAdmin()

$dataProvider=new CActiveDataProvider(rsquoPostrsquo array(

rsquosortrsquo=gtarray(

rsquodefaultOrderrsquo=gtrsquostatus update time DESCrsquo

)

))

$this-gtrender(rsquoadminrsquoarray(

rsquodataProviderrsquo=gt$dataProvider

))

34 Managing Posts 29

The above code is very similar to that in actionIndex() The main difference is in thequery criteria Here we do not specify any particular query criteria since we want to seeall posts We do customize the sort property of the data provider so that by default theposts are shown in the order of their statuses and update time

Below is the code for the admin view

lth1gtManage Postslth1gt

ltphp $this-gtwidget(rsquoziiwidgetsgridCGridViewrsquo array(

rsquodataProviderrsquo=gt$dataProvider

rsquocolumnsrsquo=gtarray(

array(

rsquonamersquo=gtrsquotitlersquo

rsquotypersquo=gtrsquorawrsquo

rsquovaluersquo=gtrsquoCHtmllink(CHtmlencode($data-gttitle) $data-gturl)rsquo

)

array(

rsquonamersquo=gtrsquostatusrsquo

rsquovaluersquo=gtrsquoLookupitem(PostStatus$data-gtstatus)rsquo

)

rsquocreate timedatetimersquo

rsquoupdate timedatetimersquo

array(

rsquoclassrsquo=gtrsquoCButtonColumnrsquo

)

)

)) gt

We use [CGridView] to display the posts It allows us to sort by a column and paginatethrough the posts if there are too many to be displayed in a single page Our change ismainly about how to display each column For example for the title column we specifythat it should be displayed as a hyperlink that points to the detailed view of the post

Tip When displaying text we call CHtmlencode() to encode HTML entities init This prevents from cross-site scripting attack

342 Deleting Posts

In the admin data grid there is a delete button in each row Clicking on the button shoulddelete the corresponding post Internally this triggers the delete action implemented asfollows

30 3 Post Management

public function actionDelete()

if(Yiiapp()-gtrequest-gtisPostRequest)

we only allow deletion via POST request

$this-gtloadModel()-gtdelete()

if(isset($ POST[rsquoajaxrsquo]))

$this-gtredirect(array(rsquoindexrsquo))

else

throw new CHttpException(400rsquoInvalid request Please do not repeat this request againrsquo)

The above code is the one generated by the yiic tool without any change We wouldlike to explain a little bit more about the checking on $ POST[rsquoajaxrsquo] The [CGridView]widget has a very nice feature that its sorting pagination and deletion operations are alldone in AJAX mode by default That means the whole page does not get reloaded if anyof the above operations is performed However it is also possible that the widget runs innon-AJAX mode (by setting its ajaxUpdate property to be false or disabling JavaScript onthe client side) It is necessary for the delete action to differentiate these two scenarios ifthe delete request is made via AJAX we should not redirect the user browser otherwisewe should

Deleting a post should also cause the deletion of all comments for that post In additionwe should also update the tbl tag table regarding the tags for the deleted post Both ofthese tasks can be achieved by writing an afterDelete method in the Post model class asfollows

protected function afterDelete()

parentafterDelete()

Commentmodel()-gtdeleteAll(rsquopost id=rsquo$this-gtid)

Tagmodel()-gtupdateFrequency($this-gttags rsquorsquo)

The above code is very straightforward it first deletes all those comments whose post id

is the same as the ID of the deleted post it then updates the tbl tag table for the tags

of the deleted post

34 Managing Posts 31

Tip We have to explicitly delete all comments for the deleted post here becauseSQLite does not really support foreign key constraints In a DBMS that supportsthis constraint (such as MySQL PostgreSQL) the foreign key constraint can be setup such that the DBMS automatically deletes the related comments if the post isdeleted In that case we no longer this explicit deletion call in our code

32 3 Post Management

Chapter 4

Comment Management

41 Customizing Comment Model

For the Comment model we mainly need to customize the rules() and attributeLabels()

methods The attributeLabels() method returns a mapping between attribute namesand attribute labels We do not need to touch relations() since the code generated bythe yiic tool is good enough

411 Customizing rules() Method

We first customize the validation rules generated by the yiic tool The following rules areused for comments

public function rules()

return array(

array(rsquocontent author emailrsquo rsquorequiredrsquo)

array(rsquoauthor email urlrsquo rsquolengthrsquo rsquomaxrsquo=gt128)

array(rsquoemailrsquorsquoemailrsquo)

array(rsquourlrsquorsquourlrsquo)

)

In the above we specify that the author email and content attributes are required thelength of author email and url cannot exceed 128 the email attribute must be a validemail address and the url attribute must be a valid URL

412 Customizing attributeLabels() Method

We then customize the attributeLabels() method to declare the label display for eachmodel attribute This method returns an array consisting of name-label pairs When wecall CHtmlactiveLabel() to display an attribute label

34 4 Comment Management

public function attributeLabels()

return array(

rsquoidrsquo =gt rsquoIdrsquo

rsquocontentrsquo =gt rsquoCommentrsquo

rsquostatusrsquo =gt rsquoStatusrsquo

rsquocreate timersquo =gt rsquoCreate Timersquo

rsquoauthorrsquo =gt rsquoNamersquo

rsquoemailrsquo =gt rsquoEmailrsquo

rsquourlrsquo =gt rsquoWebsitersquo

rsquopost idrsquo =gt rsquoPostrsquo

)

Tip If the label for an attribute is not declared in attributeLabels() an algo-rithm will be used to generate an appropriate label For example a label VerifyCode will be generated for attributes verify code or verifyCode

413 Customizing Saving Process

Because we want to record the creation time of a comment we override the beforeSave()

method of Comment like we do for the Post model

protected function beforeSave()

if(parentbeforeSave())

if($this-gtisNewRecord)

$this-gtcreate time=time()

return true

else

return false

42 Creating and Displaying Comments

In this section we implement the comment display and creation features

421 Displaying Comments

Instead of displaying and creating comments on individual pages we use the post detailpage (generated by the view action of PostController) Below the post content display

42 Creating and Displaying Comments 35

we display first a list of comments belonging to that post and then a comment creationform

In order to display comments on the post detail page we modify the view script wwwrootblogprotectedviewspostviewphp as follows

ltdiv id=commentsgt

ltphp if($model-gtcommentCountgt=1) gt

lth3gt

ltphp echo $model-gtcommentCount rsquocomment(s)rsquo gt

lth3gt

ltphp $this-gtrenderPartial(rsquo commentsrsquoarray(

rsquopostrsquo=gt$model

rsquocommentsrsquo=gt$model-gtcomments

)) gt

ltphp endif gt

ltdivgt

In the above we call renderPartial() to render a partial view named comments to displaythe list of comments belonging to the current post Note that in the view we use theexpression $model-gtcomments to retrieve the comments for the post This is valid becausewe have declared a comments relation in the Post class Evaluating this expression wouldtrigger an implicit JOIN database query to bring back the proper comments This featureis known as lazy relational query

The partial view comments is not very interesting It mainly goes through every commentand displays the detail of it Interested readers may refer to wwwrootyiidemosblog

protectedpost commentsphp

422 Creating Comments

To handle comment creation we first modify the actionView() method of PostController

as follows

public function actionView()

$post=$this-gtloadModel()

$comment=$this-gtnewComment($post)

$this-gtrender(rsquoviewrsquoarray(

rsquomodelrsquo=gt$post

rsquocommentrsquo=gt$comment

))

36 4 Comment Management

protected function newComment($post)

$comment=new Comment

if(isset($ POST[rsquoCommentrsquo]))

$comment-gtattributes=$ POST[rsquoCommentrsquo]

if($post-gtaddComment($comment))

if($comment-gtstatus==CommentSTATUS PENDING)

Yiiapp()-gtuser-gtsetFlash(rsquocommentSubmittedrsquorsquoThank yoursquo)

$this-gtrefresh()

return $comment

In the above we call the newComment() method before we render view In the newComment()

method we generate a Comment instance and check if the comment form is submitted If sowe try to add the comment for the post by calling $post-gtaddComment($comment) If it goesthrough we refresh the post detail page In case the comment needs to be approved we willshow a flash message to indicate this decision A flash message is usually a confirmationmessage displayed to end users If the user clicks on the refresh button of his browser themessage will disappear

We also need to modify wwwrootblogprotectedviewspostviewphp furthermore

ltdiv id=commentsgt

lth3gtLeave a Commentlth3gt

ltphp if(Yiiapp()-gtuser-gthasFlash(rsquocommentSubmittedrsquo)) gt

ltdiv class=successgt

ltphp echo Yiiapp()-gtuser-gtgetFlash(rsquocommentSubmittedrsquo) gt

ltdivgt

ltphp else gt

ltphp $this-gtrenderPartial(rsquocomment formrsquoarray(

rsquomodelrsquo=gt$comment

)) gt

ltphp endif gt

ltdivgtlt-- comments --gt

43 Managing Comments 37

In the above code we display the flash message if it is available If not we displaythe comment input form by rendering the partial view wwwrootblogprotectedviews

comment formphp

43 Managing Comments

Comment management includes updating deleting and approving comments These op-erations are implemented as actions in the CommentController class

431 Updating and Deleting Comments

The code generated by yiic for updating and deleting comments remains largely un-changed

432 Approving Comments

When comments are newly created they are in pending approval status and need to beapproved in order to be visible to guest users Approving a comment is mainly aboutchanging the status column of the comment

We create an actionApprove() method in CommentController as follows

public function actionApprove()

if(Yiiapp()-gtrequest-gtisPostRequest)

$comment=$this-gtloadModel()

$comment-gtapprove()

$this-gtredirect(array(rsquoindexrsquo))

else

throw new CHttpException(400rsquoInvalid requestrsquo)

In the above when the approve action is invoked via a POST request we call the approve()

method defined in the Comment model to change the status We then redirect the userbrowser to the page displaying the post that this comment belongs to

We also modify the actionIndex() method of Comment to show all comments We wouldlike to see comments pending approved to show up first

public function actionIndex()

38 4 Comment Management

$dataProvider=new CActiveDataProvider(rsquoCommentrsquo array(

rsquocriteriarsquo=gtarray(

rsquowithrsquo=gtrsquopostrsquo

rsquoorderrsquo=gtrsquotstatus tcreate time DESCrsquo

)

))

$this-gtrender(rsquoindexrsquoarray(

rsquodataProviderrsquo=gt$dataProvider

))

Notice that in the above code because both tbl post and tbl comment have columns statusand create time we need to disambiguate the corresponding column reference by prefixingthem with table alias names As described in the guide the alias for the primary table ina relational query is always t Therefore we are prefixing t to the status and create time

columns in the above code

Like the post index view the index view for CommentController uses [CListView] to displaythe comment list which in turn uses the partial view wwwrootblogprotectedviews

comment viewphp to display the detail of each individual comment We will not go intodetails here Interested readers may refer to the corresponding file in the blog demowwwrootyiidemosblogprotectedviewscomment viewphp

Chapter 5

Portlets

51 Customizing Page Layout

Features like rdquothe most recent commentsrdquo rdquotag cloudrdquo are better to be implemented inportlets A portlet is a pluggable user interface component that renders a fragment ofHTML code In this section we describe how to set up the portlet architecture for ourblog application

Before we go into details about developing portlets we first need to adjust our page layoutso that the portlets can be placed in a side bar

A page layout determines how to share common sections (eg header footer) amongdifferent pages and how to embed contents that are specific for different pages In Yiia page layout is represented as a view under the protectedviewslayouts folder Whenwe call the render() method in a controller the rendering result will be automaticallyembedded into this layout view before it is displayed to end users The default layoutview is named as main which means the wwwrootblogprotectedviewslayoutsmain

php file in our blog application

We could directly modify the main layout to add a side bar However doing so wouldmake some irrelevant pages such as about contact pages to have a side bar which is notalways desirable For this reason we need different layouts for different pages

We thus create two different layouts column1 and column2 The former displays the pagein a single column while the latter displays two columns in the page one of which is theside bar We will use column2 layout for the post and comment pages and column1 forthe rest of the pages Because both column1 and column2 still share the same header andfooter sections we let them to share the main layout

Based on the above description we create the column1 layout as follows

ltphp $this-gtbeginContent(rsquolayoutsmainrsquo) gt

ltdiv class=containergt

40 5 Portlets

ltdiv id=contentgt

ltphp echo $content gt

ltdivgtlt-- content --gt

ltdivgt

ltphp $this-gtendContent() gt

and the column2 layout as follows

ltphp $this-gtbeginContent(rsquolayoutsmainrsquo) gt

ltdiv class=containergt

ltdiv class=span-18gt

ltdiv id=contentgt

ltphp echo $content gt

ltdivgtlt-- content --gt

ltdivgt

ltdiv class=span-6 lastgt

ltdiv id=sidebargt

portlets to be placed here

ltdivgtlt-- sidebar --gt

ltdivgt

ltdivgt

ltphp $this-gtendContent() gt

As we can see both column1 and column2 are enclosed in a pair of beginContent andendContent calls They essentially use the specified view layoutsmain to decorate theenclosed content In all these layout views the variable $content refers to the dynamiccontent to be embedded into the layout

Besides customizing the layout view file we also need to adjust the CSS file wwwroot

blogcssmaincss so that the overall appearance would look like what we see in the blogdemo We will not go into details here

Info The skeleton application generated by the yiic tool uses the BluePrint CSSframework It provides a nice font reset and a robust grid system For more detailson how to use this framework please refer to its documentation

After creating the new layouts we need to set the layout property of controllers to makeuse of them So far we have three controllers SiteController PostController andCommentController We set SiteController to use column1 as the default layout whilethe rest two of the controllers use column2 for example

class PostController extends Controller

52 Creating User Menu Portlet 41

public $layout=rsquocolumn2rsquo

Tip If some view of the controller does not use the same layout as declaredby the layout property it may customize it in the view by a simple assignment$this-gtlayout=rsquonewlayoutrsquo

52 Creating User Menu Portlet

Based on the requirements analysis we need three different portlets the rdquouser menurdquoportlet the rdquotag cloudrdquo portlet and the rdquorecent commentsrdquo portlet We will develop theseportlets by extending the [CPortlet] widget provided by Yii

In this section we will develop our first concrete portlet - the user menu portlet whichdisplays a list of menu items that are only available to authenticated users The menucontains four items

bull Approve Comments a hyperlink that leads to a list of comments pending approval

bull Create New Post a hyperlink that leads to the post creation page

bull Manage Posts a hyperlink that leads to the post management page

bull Logout a link button that would log out the current user

521 Creating UserMenu Class

We create the UserMenu class to represent the logic part of the user menu portlet Theclass is saved in the file wwwrootblogprotectedcomponentsUserMenuphp which has thefollowing content

Yiiimport(rsquoziiwidgetsCPortletrsquo)

class UserMenu extends CPortlet

public function init()

$this-gttitle=CHtmlencode(Yiiapp()-gtuser-gtname)

parentinit()

42 5 Portlets

protected function renderContent()

$this-gtrender(rsquouserMenursquo)

The UserMenu class extends from the CPortlet class that we created previously It overridesboth the init() method and the renderContent() method of CPortlet The former setsthe portlet title to be the name of the current user the latter generates the portlet bodycontent by rendering a view named userMenu

Tip Notice that we have to explicitly include the CPortlet class by calling Yii

import() before we refer to it the first time This is because CPortlet is part of thezii project ndash the official extension library for Yii For performance considerationclasses in this project are not listed as core classes Therefore we have to import itbefore we use it the first time

522 Creating userMenu View

Next we create the userMenu view which is saved in the file wwwrootblogprotected

componentsviewsuserMenuphp

ltulgt

ltligtltphp echo CHtmllink(rsquoCreate New Postrsquoarray(rsquopostcreatersquo)) gtltligt

ltligtltphp echo CHtmllink(rsquoManage Postsrsquoarray(rsquopostadminrsquo)) gtltligt

ltligtltphp echo CHtmllink(rsquoApprove Commentsrsquoarray(rsquocommentindexrsquo))

rsquo (rsquo Commentmodel()-gtpendingCommentCount rsquo)rsquo gtltligt

ltligtltphp echo CHtmllink(rsquoLogoutrsquoarray(rsquositelogoutrsquo)) gtltligt

ltulgt

Info By default view files for a widget should be placed under the views sub-directory of the directory containing the widget class file The file name must bethe same as the view name

523 Using UserMenu Portlet

It is time for us to make use of our newly completed UserMenu portlet We modify thelayout view file wwwrootblogprotectedviewslayoutscolumn2php as follows

53 Creating Tag Cloud Portlet 43

ltdiv id=sidebargt

ltphp if(Yiiapp()-gtuser-gtisGuest) $this-gtwidget(rsquoUserMenursquo) gt

ltdivgt

In the above we call the widget() method to generate and execute an instance of theUserMenu class Because the portlet should only be displayed to authenticated users weonly call widget() when the isGuest property of the current user is false (meaning theuser is authenticated)

524 Testing UserMenu Portlet

Letrsquos test what we have so far

1 Open a browser window and enter the URL httpwwwexamplecomblogindex

php Verify that there is nothing displayed in the side bar section of the page

2 Click on the Login hyperlink and fill out the login form to login If successful verifythat the UserMenu portlet appears in the side bar and the portlet has the usernameas its title

3 Click on the rsquoLogoutrsquo hyperlink in the UserMenu portlet Verify that the logout actionis successful and the UserMenu portlet disappears

525 Summary

What we have created is a portlet that is highly reusable We can easily reuse it in adifferent project with little or no modification Moreover the design of this portlet followsclosely the philosophy that logic and presentation should be separated While we did notpoint this out in the previous sections such practice is used nearly everywhere in a typicalYii application

53 Creating Tag Cloud Portlet

Tag cloud displays a list of post tags with visual decorations hinting the popularity of eachindividual tag

531 Creating TagCloud Class

We create the TagCloud class in the file wwwrootblogprotectedcomponentsTagCloud

php The file has the following content

44 5 Portlets

Yiiimport(rsquoziiwidgetsCPortletrsquo)

class TagCloud extends CPortlet

public $title=rsquoTagsrsquo

public $maxTags=20

protected function renderContent()

$tags=Tagmodel()-gtfindTagWeights($this-gtmaxTags)

foreach($tags as $tag=gt$weight)

$link=CHtmllink(CHtmlencode($tag) array(rsquopostindexrsquorsquotagrsquo=gt$tag))

echo CHtmltag(rsquospanrsquo array(

rsquoclassrsquo=gtrsquotagrsquo

rsquostylersquo=gtfont-size$weightpt) $link)n

Unlike the UserMenu portlet the TagCloud portlet does not use a view Instead its presen-tation is done in the renderContent() method This is because the presentation does notcontain much HTML tags

We display each tag as a hyperlink to the post index page with the corresponding tagparameter The font size of each tag link is adjusted according to their relative weightamong other tags If a tag has higher frequency value than the other it will have a biggerfont size

532 Using TagCloud Portlet

Usage of the TagCloud portlet is very simple We modify the layout file wwwrootblog

protectedviewslayoutsmainphp as follows

ltdiv id=sidebargt

ltphp if(Yiiapp()-gtuser-gtisGuest) $this-gtwidget(rsquoUserMenursquo) gt

ltphp $this-gtwidget(rsquoTagCloudrsquo array(

rsquomaxTagsrsquo=gtYiiapp()-gtparams[rsquotagCloudCountrsquo]

)) gt

ltdivgt

54 Creating Recent Comments Portlet 45

54 Creating Recent Comments Portlet

In this section we create the last portlet that displays a list of comments recently pub-lished

541 Creating RecentComments Class

We create the RecentComments class in the file wwwrootblogprotectedcomponentsRecentCommentsphp The file has the following content

Yiiimport(rsquoziiwidgetsCPortletrsquo)

class RecentComments extends CPortlet

public $title=rsquoRecent Commentsrsquo

public $maxComments=10

public function getRecentComments()

return Commentmodel()-gtfindRecentComments($this-gtmaxComments)

protected function renderContent()

$this-gtrender(rsquorecentCommentsrsquo)

In the above we invoke the findRecentComments method which is defined in the Comment

class as follows

class Comment extends CActiveRecord

public function findRecentComments($limit=10)

return $this-gtwith(rsquopostrsquo)-gtfindAll(array(

rsquoconditionrsquo=gtrsquotstatus=rsquoselfSTATUS APPROVED

rsquoorderrsquo=gtrsquotcreate time DESCrsquo

rsquolimitrsquo=gt$limit

))

46 5 Portlets

542 Creating recentComments View

The recentComments view is saved in the file wwwrootblogprotectedcomponentsviews

recentCommentsphp It simply displays every comment returned by the RecentComments

getRecentComments() method

543 Using RecentComments Portlet

We modify the layout file wwwrootblogprotectedviewslayoutsmainphp to embedthis last portlet

ltdiv id=sidebargt

ltphp if(Yiiapp()-gtuser-gtisGuest) $this-gtwidget(rsquoUserMenursquo) gt

ltphp $this-gtwidget(rsquoTagCloudrsquo array(

rsquomaxTagsrsquo=gtYiiapp()-gtparams[rsquotagCloudCountrsquo]

)) gt

ltphp $this-gtwidget(rsquoRecentCommentsrsquo array(

rsquomaxCommentsrsquo=gtYiiapp()-gtparams[rsquorecentCommentCountrsquo]

)) gt

ltdivgt

Chapter 6

Final Work

61 Beautifying URLs

The URLs linking various pages of our blog application currently look ugly For examplethe URL for the page showing a post looks like the following

indexphpr=postshowampid=1

In this section we describe how to beautify these URLs and make them SEO-friendlyOur goal is to be able to use the following URLs in the application

1 indexphppostsyii leads to the page showing a list of posts with tag yii

2 indexphppost2A+Test+Post leads to the page showing the detail of the postwith ID 2 whose title is A Test Post

3 indexphppostupdate1 leads to the page that allows updating the post with ID1

Note that in the second URL format we include the post title in the URL This is mainlyto make the URL SEO friendly It is said that search engines may also respect the wordsfound in a URL when it is being indexed

To achieve our goal we modify the application configuration as follows

return array(

rsquocomponentsrsquo=gtarray(

rsquourlManagerrsquo=gtarray(

rsquourlFormatrsquo=gtrsquopathrsquo

rsquorulesrsquo=gtarray(

48 6 Final Work

rsquopostltidd+gtlttitlegtrsquo=gtrsquopostviewrsquorsquopostslttaggtrsquo=gtrsquopostindexrsquo

)

)

)

)

In the above we configure the urlManager component by setting its urlFormat propertyto be path and adding a set of rules

The rules are used by urlManager to parse and create the URLs in the desired formatFor example the first rule says that if a URL indexphppostsyii is requested theurlManager component should be responsible to dispatch the request to the route post

index and generate a tag GET parameter with the value yii On the other hand whencreating a URL with the route postindex and parameter tag the urlManager componentwill also use this rule to generate the desired URL indexphppostsyii For this reasonwe say that urlManager is a two-way URL manager

The urlManager component can further beautify our URLs such as hiding indexphp inthe URLs appending suffix like html to the URLs We can obtain these features easilyby configuring various properties of urlManager in the application configuration For moredetails please refer to the Guide

62 Logging Errors

A production Web application often needs sophisticated logging for various events In ourblog application we would like to log the errors occurring when it is being used Sucherrors could be programming mistakes or usersrsquo misuse of the system Logging these errorswill help us to improve the blog application

We enable the error logging by modifying the application configuration as follows

return array(

rsquopreloadrsquo=gtarray(rsquologrsquo)

rsquocomponentsrsquo=gtarray(

rsquologrsquo=gtarray(

rsquoclassrsquo=gtrsquoCLogRouterrsquo

rsquoroutesrsquo=gtarray(

array(

rsquoclassrsquo=gtrsquoCFileLogRoutersquo

rsquolevelsrsquo=gtrsquoerror warningrsquo

63 Final Tune-up and Deployment 49

)

)

)

)

)

With the above configuration if an error or warning occurs detailed information will belogged and saved in a file located under the directory wwwrootblogprotectedruntime

The log component offers more advanced features such as sending log messages to a listof email addresses displaying log messages in JavaScript console window etc For moredetails please refer to the Guide

63 Final Tune-up and Deployment

We are close to finish our blog application Before deployment we would like to do sometune-ups

631 Changing Home Page

We change to use the post list page as the home page We modify the application config-uration as follows

return array(

rsquodefaultControllerrsquo=gtrsquopostrsquo

)

Tip Because PostController already declares list to be its default action whenwe access the home page of the application we will see the result generated by thelist action of the post controller

632 Enabling Schema Caching

Because ActiveRecord relies on the metadata about tables to determine the column infor-mation it takes time to read the metadata and analyze it This may not be a problemduring development stage but for an application running in production mode it is a totalwaste of time if the database schema does not change Therefore we should enable theschema caching by modifying the application configuration as follows

50 6 Final Work

return array(

rsquocomponentsrsquo=gtarray(

rsquocachersquo=gtarray(

rsquoclassrsquo=gtrsquoCDbCachersquo

)

rsquodbrsquo=gtarray(

rsquoclassrsquo=gtrsquosystemdbCDbConnectionrsquo

rsquoconnectionStringrsquo=gtrsquosqlitewwwrootblogprotecteddatablogdbrsquo

rsquoschemaCachingDurationrsquo=gt3600

)

)

)

In the above we first add a cache component which uses a default SQLite database as thecaching storage If our server is equipped with other caching extensions such as APCwe could change to use them as well We also modify the db component by setting itsschemaCachingDuration property to be 3600 which means the parsed database schemadata can remain valid in cache for 3600 seconds

633 Disabling Debugging Mode

We modify the entry script file wwwrootblogindexphp by removing the line definingthe constant YII DEBUG This constant is useful during development stage because it allowsYii to display more debugging information when an error occurs However when theapplication is running in production mode displaying debugging information is not agood idea because it may contain sensitive information such as where the script file islocated and the content in the file etc

634 Deploying the Application

The final deployment process manly involves copying the directory wwwrootblog to thetarget directory The following checklist shows every needed step

1 Install Yii in the target place if it is not available

2 Copy the entire directory wwwrootblog to the target place

3 Edit the entry script file indexphp by pointing the $yii variable to the new Yiibootstrap file

4 Edit the file protectedyiicphp by setting the $yiic variable to be the new Yiiyiicphp file

64 Future Enhancements 51

5 Change the permission of the directories assets and protectedruntime so that theyare writable by the Web server process

64 Future Enhancements

641 Using a Theme

Without writing any code our blog application is already themeable To use a themewe mainly need to develop the theme by writing customized view files in the theme Forexample to use a theme named classic that uses a different page layout we would createa layout view file wwwrootblogthemesclassicviewslayoutsmainphp We also needto change the application configuration to indicate our choice of the classic theme

return array(

rsquothemersquo=gtrsquoclassicrsquo

)

642 Internationalization

We may also internationalize our blog application so that its pages can be displayed indifferent languages This mainly involves efforts in two aspects

First we may create view files in different languages For example for the list pageof PostController we can create a view file wwwrootblogprotectedviewspostzh cn

listphp When the application is configured to use simplified Chinese (the language codeis zh cn) Yii will automatically use this new view file instead of the original one

Second we may create message translations for those messages generated by code Themessage translations should be saved as files under the directory wwwrootblogprotected

messages We also need to modify the code where we use text strings by enclosing themin the method call Yiit()

For more details about internationalization please refer to the Guide

643 Improving Performance with Cache

While the Yii framework itself is very efficient it is not necessarily true that an applicationwritten in Yii efficient There are several places in our blog application that we can improvethe performance For example the tag clould portlet could be one of the performancebottlenecks because it involves complex database query and PHP logic

52 6 Final Work

We can make use of the sophisticated caching feature provided by Yii to improve theperformance One of the most useful components in Yii is COutputCache which caches afragment of page display so that the underlying code generating the fragment does not needto be executed for every request For example in the layout file wwwrootblogprotected

viewslayoutsmainphp we can enclose the tag cloud portlet with COutputCache

ltphp if($this-gtbeginCache(rsquotagCloudrsquo array(rsquodurationrsquo=gt3600))) gt

ltphp $this-gtwidget(rsquoTagCloudrsquo array(

rsquomaxTagsrsquo=gtYiiapp()-gtparams[rsquotagCloudCountrsquo]

)) gt

ltphp $this-gtendCache() gt

With the above code the tag cloud display will be served from cache instead of beinggenerated on-the-fly for every request The cached content will remain valid in cache for3600 seconds

644 Adding New Features

Our blog application only has very basic functionalities To become a complete blogsystem more features are needed for example calendar portlet email notifications postcategorization archived post portlet and so on We will leave the implementation of thesefeatures to interested readers

  • Contents
  • License
  • Getting Started
    • Building a Blog System using Yii
    • Testdriving with Yii
      • Installing Yii
      • Creating Skeleton Application
      • Application Workflow
        • Requirements Analysis
        • Overall Design
          • Initial Prototyping
            • Setting Up Database
              • Creating Database
              • Establishing Database Connection
                • Scaffolding
                • Authenticating User
                • Summary
                  • Post Management
                    • Customizing Post Model
                      • Customizing rules() Method
                      • Customizing relations() Method
                      • Representing Status in Text
                        • Creating and Updating Posts
                          • Customizing Access Control
                          • Customizing create and update Operations
                            • Displaying Posts
                              • Customizing view Operation
                              • Customizing index Operation
                                • Managing Posts
                                  • Listing Posts in Tabular View
                                  • Deleting Posts
                                      • Comment Management
                                        • Customizing Comment Model
                                          • Customizing rules() Method
                                          • Customizing attributeLabels() Method
                                          • Customizing Saving Process
                                            • Creating and Displaying Comments
                                              • Displaying Comments
                                              • Creating Comments
                                                • Managing Comments
                                                  • Updating and Deleting Comments
                                                  • Approving Comments
                                                      • Portlets
                                                        • Customizing Page Layout
                                                        • Creating User Menu Portlet
                                                          • Creating UserMenu Class
                                                          • Creating userMenu View
                                                          • Using UserMenu Portlet
                                                          • Testing UserMenu Portlet
                                                          • Summary
                                                            • Creating Tag Cloud Portlet
                                                              • Creating TagCloud Class
                                                              • Using TagCloud Portlet
                                                                • Creating Recent Comments Portlet
                                                                  • Creating RecentComments Class
                                                                  • Creating recentComments View
                                                                  • Using RecentComments Portlet
                                                                      • Final Work
                                                                        • Beautifying URLs
                                                                        • Logging Errors
                                                                        • Final Tune-up and Deployment
                                                                          • Changing Home Page
                                                                          • Enabling Schema Caching
                                                                          • Disabling Debugging Mode
                                                                          • Deploying the Application
                                                                            • Future Enhancements
                                                                              • Using a Theme
                                                                              • Internationalization
                                                                              • Improving Performance with Cache
                                                                              • Adding New Features
Page 32: Yii Blog 1.1.0 Demo Tutor

33 Displaying Posts 25

When we save a post we want to update the tbl tag table to reflect the change of tagfrequencies We can do this work in the afterSave() method which is automaticallyinvoked by Yii after a post is successfully saved into the database

protected function afterSave()

parentafterSave()

Tagmodel()-gtupdateFrequency($this-gt oldTags $this-gttags)

private $ oldTags

protected function afterFind()

parentafterFind()

$this-gt oldTags=$this-gttags

In the implementation because we want to detect if the user changes the tags in case he isupdating an existing post we need to know what the old tags are For this reason we alsowrite the afterFind() method to keep the old tags in the variable oldTags The methodafterFind() is invoked automatically by Yii when an AR record is populated with thedata from database

We are not going to give details of the TagupdateFrequency() method here Interestedreaders may refer to the file wwwrootyiidemosblogprotectedmodelsTagphp

33 Displaying Posts

In our blog application a post may be displayed among a list of posts or by itself Theformer is implemented as the index operation while the latter the view operation In thissection we customize both operations to fulfill our initial requirements

331 Customizing view Operation

The view operation is implemented by the actionView() method in PostController Itsdisplay is generated by the view view with the view file wwwrootblogprotectedviews

postviewphp

Below is the relevant code implementing the view operation in PostController

public function actionView()

26 3 Post Management

$this-gtrender(rsquoviewrsquoarray(

$post=$this-gtloadModel()

$this-gtrender(rsquoviewrsquoarray(

rsquomodelrsquo=gt$post

))

))

private $ model

public function loadModel()

if($this-gt model===null)

if(isset($ GET[rsquoidrsquo]))

if(Yiiapp()-gtuser-gtisGuest)

$condition=rsquostatus=rsquoPostSTATUS PUBLISHED

rsquo OR status=rsquoPOSTSTATUS ARCHIVED

else

$condition=rsquorsquo

$this-gt model=Postmodel()-gtfindbyPk($ GET[rsquoidrsquo] $condition)

if($this-gt model===null)

throw new CHttpException(404rsquoThe requested page does not existrsquo)

return $this-gt model

Our change mainly lies in the loadModel() method In this method we query the Post

table according to the id GET parameter If the post is not found or if it is not publishedor archived (when the user is a guest) we will throw a 404 HTTP error Otherwise thepost object is returned to actionView() which in turn passes the post object to the viewscript for further display

Tip Yii captures HTTP exceptions (instances of CHttpException) and displaysthem in either predefined templates or customized error views The skeleton appli-cation generated by yiic already contains a customized error view in wwwroot

blogprotectedviewssiteerrorphp We can modify this file if we want tofurther customize the error display

The change in the view script is mainly about ajdusting the formatting and styles of thepost display We will not go into details here Interested readers may refer to wwwroot

blogprotectedviewspostviewphp

33 Displaying Posts 27

332 Customizing index Operation

Like the view operation we customize the index operation in two places the actionIndex()

method in PostController and the view file wwwrootblogprotectedviewspostindex

php We mainly need to add the support for displaying a list of posts that are associatedwith a specified tag

Below is the modified actionIndex() method in PostController

public function actionIndex()

$criteria=new CDbCriteria(array(

rsquoconditionrsquo=gtrsquostatus=rsquoPostSTATUS PUBLISHED

rsquoorderrsquo=gtrsquoupdate time DESCrsquo

rsquowithrsquo=gtrsquocommentCountrsquo

))

if(isset($ GET[rsquotagrsquo]))

$criteria-gtaddSearchCondition(rsquotagsrsquo$ GET[rsquotagrsquo])

$dataProvider=new CActiveDataProvider(rsquoPostrsquo array(

rsquopaginationrsquo=gtarray(

rsquopageSizersquo=gtYiiapp()-gtparams[rsquopostsPerPagersquo]

)

rsquocriteriarsquo=gt$criteria

))

$this-gtrender(rsquoindexrsquoarray(

rsquodataProviderrsquo=gt$dataProvider

))

In the above we first create a query criteria for retrieving post list The criteria statesthat only published posts should be returned and they should be sorted according to theirupdate time in descending order Because when displaying a post in the list we want toshow how many comments the post has received in the criteria we also specify to bringback commentCount which if you remember is a relation declared in Postrelations()

In case when a user wants to see posts with a specific tag we would add a search conditionto the criteria to look for the specified tag

Using the query criteria we create a data provider which mainly serves for three purposesFirst it does pagination of the data when too many results may be returned Herewe customize the pagination by setting the page size to be an application parameterpostsPerPage Second it does sorting according to the user request And finally it feedsthe paginated and sorted data to widgets or view code for presentation

28 3 Post Management

After we finish with actionIndex() we modify the index view as follows Our change ismainly about adding the h1 header when the user specifies to display posts with a tag

ltphp if(empty($ GET[rsquotagrsquo])) gt

lth1gtPosts Tagged with ltigtltphp echo CHtmlencode($ GET[rsquotagrsquo]) gtltigtlth1gt

ltphp endif gt

ltphp $this-gtwidget(rsquoziiwidgetsCListViewrsquo array(

rsquodataProviderrsquo=gt$dataProvider

rsquoitemViewrsquo=gtrsquo viewrsquo

rsquotemplatersquo=gtitemsnpager)) gt

Note that in the above we use [CListView] to display the post list This widget requires apartial view to display the detail of each individual post Here we specify the partial viewto be view which means the file wwwrootblogprotectedviewspost viewphp In thisview script we can acccess the post instance being displayed via a local variable named$data

34 Managing Posts

Managing posts mainly refers to listing posts in an administrative view that allows us tosee posts with all statuses updating them and deleting them They are accomplished bythe admin operation and the delete operation respectively The code generated by yiic

does not need much modification Below we mainly explain how these two operations areimplemented

341 Listing Posts in Tabular View

The admin operation shows posts with all statuses in a tabular view The view supportssorting and pagination The following is the actionAdmin() method in PostController

public function actionAdmin()

$dataProvider=new CActiveDataProvider(rsquoPostrsquo array(

rsquosortrsquo=gtarray(

rsquodefaultOrderrsquo=gtrsquostatus update time DESCrsquo

)

))

$this-gtrender(rsquoadminrsquoarray(

rsquodataProviderrsquo=gt$dataProvider

))

34 Managing Posts 29

The above code is very similar to that in actionIndex() The main difference is in thequery criteria Here we do not specify any particular query criteria since we want to seeall posts We do customize the sort property of the data provider so that by default theposts are shown in the order of their statuses and update time

Below is the code for the admin view

lth1gtManage Postslth1gt

ltphp $this-gtwidget(rsquoziiwidgetsgridCGridViewrsquo array(

rsquodataProviderrsquo=gt$dataProvider

rsquocolumnsrsquo=gtarray(

array(

rsquonamersquo=gtrsquotitlersquo

rsquotypersquo=gtrsquorawrsquo

rsquovaluersquo=gtrsquoCHtmllink(CHtmlencode($data-gttitle) $data-gturl)rsquo

)

array(

rsquonamersquo=gtrsquostatusrsquo

rsquovaluersquo=gtrsquoLookupitem(PostStatus$data-gtstatus)rsquo

)

rsquocreate timedatetimersquo

rsquoupdate timedatetimersquo

array(

rsquoclassrsquo=gtrsquoCButtonColumnrsquo

)

)

)) gt

We use [CGridView] to display the posts It allows us to sort by a column and paginatethrough the posts if there are too many to be displayed in a single page Our change ismainly about how to display each column For example for the title column we specifythat it should be displayed as a hyperlink that points to the detailed view of the post

Tip When displaying text we call CHtmlencode() to encode HTML entities init This prevents from cross-site scripting attack

342 Deleting Posts

In the admin data grid there is a delete button in each row Clicking on the button shoulddelete the corresponding post Internally this triggers the delete action implemented asfollows

30 3 Post Management

public function actionDelete()

if(Yiiapp()-gtrequest-gtisPostRequest)

we only allow deletion via POST request

$this-gtloadModel()-gtdelete()

if(isset($ POST[rsquoajaxrsquo]))

$this-gtredirect(array(rsquoindexrsquo))

else

throw new CHttpException(400rsquoInvalid request Please do not repeat this request againrsquo)

The above code is the one generated by the yiic tool without any change We wouldlike to explain a little bit more about the checking on $ POST[rsquoajaxrsquo] The [CGridView]widget has a very nice feature that its sorting pagination and deletion operations are alldone in AJAX mode by default That means the whole page does not get reloaded if anyof the above operations is performed However it is also possible that the widget runs innon-AJAX mode (by setting its ajaxUpdate property to be false or disabling JavaScript onthe client side) It is necessary for the delete action to differentiate these two scenarios ifthe delete request is made via AJAX we should not redirect the user browser otherwisewe should

Deleting a post should also cause the deletion of all comments for that post In additionwe should also update the tbl tag table regarding the tags for the deleted post Both ofthese tasks can be achieved by writing an afterDelete method in the Post model class asfollows

protected function afterDelete()

parentafterDelete()

Commentmodel()-gtdeleteAll(rsquopost id=rsquo$this-gtid)

Tagmodel()-gtupdateFrequency($this-gttags rsquorsquo)

The above code is very straightforward it first deletes all those comments whose post id

is the same as the ID of the deleted post it then updates the tbl tag table for the tags

of the deleted post

34 Managing Posts 31

Tip We have to explicitly delete all comments for the deleted post here becauseSQLite does not really support foreign key constraints In a DBMS that supportsthis constraint (such as MySQL PostgreSQL) the foreign key constraint can be setup such that the DBMS automatically deletes the related comments if the post isdeleted In that case we no longer this explicit deletion call in our code

32 3 Post Management

Chapter 4

Comment Management

41 Customizing Comment Model

For the Comment model we mainly need to customize the rules() and attributeLabels()

methods The attributeLabels() method returns a mapping between attribute namesand attribute labels We do not need to touch relations() since the code generated bythe yiic tool is good enough

411 Customizing rules() Method

We first customize the validation rules generated by the yiic tool The following rules areused for comments

public function rules()

return array(

array(rsquocontent author emailrsquo rsquorequiredrsquo)

array(rsquoauthor email urlrsquo rsquolengthrsquo rsquomaxrsquo=gt128)

array(rsquoemailrsquorsquoemailrsquo)

array(rsquourlrsquorsquourlrsquo)

)

In the above we specify that the author email and content attributes are required thelength of author email and url cannot exceed 128 the email attribute must be a validemail address and the url attribute must be a valid URL

412 Customizing attributeLabels() Method

We then customize the attributeLabels() method to declare the label display for eachmodel attribute This method returns an array consisting of name-label pairs When wecall CHtmlactiveLabel() to display an attribute label

34 4 Comment Management

public function attributeLabels()

return array(

rsquoidrsquo =gt rsquoIdrsquo

rsquocontentrsquo =gt rsquoCommentrsquo

rsquostatusrsquo =gt rsquoStatusrsquo

rsquocreate timersquo =gt rsquoCreate Timersquo

rsquoauthorrsquo =gt rsquoNamersquo

rsquoemailrsquo =gt rsquoEmailrsquo

rsquourlrsquo =gt rsquoWebsitersquo

rsquopost idrsquo =gt rsquoPostrsquo

)

Tip If the label for an attribute is not declared in attributeLabels() an algo-rithm will be used to generate an appropriate label For example a label VerifyCode will be generated for attributes verify code or verifyCode

413 Customizing Saving Process

Because we want to record the creation time of a comment we override the beforeSave()

method of Comment like we do for the Post model

protected function beforeSave()

if(parentbeforeSave())

if($this-gtisNewRecord)

$this-gtcreate time=time()

return true

else

return false

42 Creating and Displaying Comments

In this section we implement the comment display and creation features

421 Displaying Comments

Instead of displaying and creating comments on individual pages we use the post detailpage (generated by the view action of PostController) Below the post content display

42 Creating and Displaying Comments 35

we display first a list of comments belonging to that post and then a comment creationform

In order to display comments on the post detail page we modify the view script wwwrootblogprotectedviewspostviewphp as follows

ltdiv id=commentsgt

ltphp if($model-gtcommentCountgt=1) gt

lth3gt

ltphp echo $model-gtcommentCount rsquocomment(s)rsquo gt

lth3gt

ltphp $this-gtrenderPartial(rsquo commentsrsquoarray(

rsquopostrsquo=gt$model

rsquocommentsrsquo=gt$model-gtcomments

)) gt

ltphp endif gt

ltdivgt

In the above we call renderPartial() to render a partial view named comments to displaythe list of comments belonging to the current post Note that in the view we use theexpression $model-gtcomments to retrieve the comments for the post This is valid becausewe have declared a comments relation in the Post class Evaluating this expression wouldtrigger an implicit JOIN database query to bring back the proper comments This featureis known as lazy relational query

The partial view comments is not very interesting It mainly goes through every commentand displays the detail of it Interested readers may refer to wwwrootyiidemosblog

protectedpost commentsphp

422 Creating Comments

To handle comment creation we first modify the actionView() method of PostController

as follows

public function actionView()

$post=$this-gtloadModel()

$comment=$this-gtnewComment($post)

$this-gtrender(rsquoviewrsquoarray(

rsquomodelrsquo=gt$post

rsquocommentrsquo=gt$comment

))

36 4 Comment Management

protected function newComment($post)

$comment=new Comment

if(isset($ POST[rsquoCommentrsquo]))

$comment-gtattributes=$ POST[rsquoCommentrsquo]

if($post-gtaddComment($comment))

if($comment-gtstatus==CommentSTATUS PENDING)

Yiiapp()-gtuser-gtsetFlash(rsquocommentSubmittedrsquorsquoThank yoursquo)

$this-gtrefresh()

return $comment

In the above we call the newComment() method before we render view In the newComment()

method we generate a Comment instance and check if the comment form is submitted If sowe try to add the comment for the post by calling $post-gtaddComment($comment) If it goesthrough we refresh the post detail page In case the comment needs to be approved we willshow a flash message to indicate this decision A flash message is usually a confirmationmessage displayed to end users If the user clicks on the refresh button of his browser themessage will disappear

We also need to modify wwwrootblogprotectedviewspostviewphp furthermore

ltdiv id=commentsgt

lth3gtLeave a Commentlth3gt

ltphp if(Yiiapp()-gtuser-gthasFlash(rsquocommentSubmittedrsquo)) gt

ltdiv class=successgt

ltphp echo Yiiapp()-gtuser-gtgetFlash(rsquocommentSubmittedrsquo) gt

ltdivgt

ltphp else gt

ltphp $this-gtrenderPartial(rsquocomment formrsquoarray(

rsquomodelrsquo=gt$comment

)) gt

ltphp endif gt

ltdivgtlt-- comments --gt

43 Managing Comments 37

In the above code we display the flash message if it is available If not we displaythe comment input form by rendering the partial view wwwrootblogprotectedviews

comment formphp

43 Managing Comments

Comment management includes updating deleting and approving comments These op-erations are implemented as actions in the CommentController class

431 Updating and Deleting Comments

The code generated by yiic for updating and deleting comments remains largely un-changed

432 Approving Comments

When comments are newly created they are in pending approval status and need to beapproved in order to be visible to guest users Approving a comment is mainly aboutchanging the status column of the comment

We create an actionApprove() method in CommentController as follows

public function actionApprove()

if(Yiiapp()-gtrequest-gtisPostRequest)

$comment=$this-gtloadModel()

$comment-gtapprove()

$this-gtredirect(array(rsquoindexrsquo))

else

throw new CHttpException(400rsquoInvalid requestrsquo)

In the above when the approve action is invoked via a POST request we call the approve()

method defined in the Comment model to change the status We then redirect the userbrowser to the page displaying the post that this comment belongs to

We also modify the actionIndex() method of Comment to show all comments We wouldlike to see comments pending approved to show up first

public function actionIndex()

38 4 Comment Management

$dataProvider=new CActiveDataProvider(rsquoCommentrsquo array(

rsquocriteriarsquo=gtarray(

rsquowithrsquo=gtrsquopostrsquo

rsquoorderrsquo=gtrsquotstatus tcreate time DESCrsquo

)

))

$this-gtrender(rsquoindexrsquoarray(

rsquodataProviderrsquo=gt$dataProvider

))

Notice that in the above code because both tbl post and tbl comment have columns statusand create time we need to disambiguate the corresponding column reference by prefixingthem with table alias names As described in the guide the alias for the primary table ina relational query is always t Therefore we are prefixing t to the status and create time

columns in the above code

Like the post index view the index view for CommentController uses [CListView] to displaythe comment list which in turn uses the partial view wwwrootblogprotectedviews

comment viewphp to display the detail of each individual comment We will not go intodetails here Interested readers may refer to the corresponding file in the blog demowwwrootyiidemosblogprotectedviewscomment viewphp

Chapter 5

Portlets

51 Customizing Page Layout

Features like rdquothe most recent commentsrdquo rdquotag cloudrdquo are better to be implemented inportlets A portlet is a pluggable user interface component that renders a fragment ofHTML code In this section we describe how to set up the portlet architecture for ourblog application

Before we go into details about developing portlets we first need to adjust our page layoutso that the portlets can be placed in a side bar

A page layout determines how to share common sections (eg header footer) amongdifferent pages and how to embed contents that are specific for different pages In Yiia page layout is represented as a view under the protectedviewslayouts folder Whenwe call the render() method in a controller the rendering result will be automaticallyembedded into this layout view before it is displayed to end users The default layoutview is named as main which means the wwwrootblogprotectedviewslayoutsmain

php file in our blog application

We could directly modify the main layout to add a side bar However doing so wouldmake some irrelevant pages such as about contact pages to have a side bar which is notalways desirable For this reason we need different layouts for different pages

We thus create two different layouts column1 and column2 The former displays the pagein a single column while the latter displays two columns in the page one of which is theside bar We will use column2 layout for the post and comment pages and column1 forthe rest of the pages Because both column1 and column2 still share the same header andfooter sections we let them to share the main layout

Based on the above description we create the column1 layout as follows

ltphp $this-gtbeginContent(rsquolayoutsmainrsquo) gt

ltdiv class=containergt

40 5 Portlets

ltdiv id=contentgt

ltphp echo $content gt

ltdivgtlt-- content --gt

ltdivgt

ltphp $this-gtendContent() gt

and the column2 layout as follows

ltphp $this-gtbeginContent(rsquolayoutsmainrsquo) gt

ltdiv class=containergt

ltdiv class=span-18gt

ltdiv id=contentgt

ltphp echo $content gt

ltdivgtlt-- content --gt

ltdivgt

ltdiv class=span-6 lastgt

ltdiv id=sidebargt

portlets to be placed here

ltdivgtlt-- sidebar --gt

ltdivgt

ltdivgt

ltphp $this-gtendContent() gt

As we can see both column1 and column2 are enclosed in a pair of beginContent andendContent calls They essentially use the specified view layoutsmain to decorate theenclosed content In all these layout views the variable $content refers to the dynamiccontent to be embedded into the layout

Besides customizing the layout view file we also need to adjust the CSS file wwwroot

blogcssmaincss so that the overall appearance would look like what we see in the blogdemo We will not go into details here

Info The skeleton application generated by the yiic tool uses the BluePrint CSSframework It provides a nice font reset and a robust grid system For more detailson how to use this framework please refer to its documentation

After creating the new layouts we need to set the layout property of controllers to makeuse of them So far we have three controllers SiteController PostController andCommentController We set SiteController to use column1 as the default layout whilethe rest two of the controllers use column2 for example

class PostController extends Controller

52 Creating User Menu Portlet 41

public $layout=rsquocolumn2rsquo

Tip If some view of the controller does not use the same layout as declaredby the layout property it may customize it in the view by a simple assignment$this-gtlayout=rsquonewlayoutrsquo

52 Creating User Menu Portlet

Based on the requirements analysis we need three different portlets the rdquouser menurdquoportlet the rdquotag cloudrdquo portlet and the rdquorecent commentsrdquo portlet We will develop theseportlets by extending the [CPortlet] widget provided by Yii

In this section we will develop our first concrete portlet - the user menu portlet whichdisplays a list of menu items that are only available to authenticated users The menucontains four items

bull Approve Comments a hyperlink that leads to a list of comments pending approval

bull Create New Post a hyperlink that leads to the post creation page

bull Manage Posts a hyperlink that leads to the post management page

bull Logout a link button that would log out the current user

521 Creating UserMenu Class

We create the UserMenu class to represent the logic part of the user menu portlet Theclass is saved in the file wwwrootblogprotectedcomponentsUserMenuphp which has thefollowing content

Yiiimport(rsquoziiwidgetsCPortletrsquo)

class UserMenu extends CPortlet

public function init()

$this-gttitle=CHtmlencode(Yiiapp()-gtuser-gtname)

parentinit()

42 5 Portlets

protected function renderContent()

$this-gtrender(rsquouserMenursquo)

The UserMenu class extends from the CPortlet class that we created previously It overridesboth the init() method and the renderContent() method of CPortlet The former setsthe portlet title to be the name of the current user the latter generates the portlet bodycontent by rendering a view named userMenu

Tip Notice that we have to explicitly include the CPortlet class by calling Yii

import() before we refer to it the first time This is because CPortlet is part of thezii project ndash the official extension library for Yii For performance considerationclasses in this project are not listed as core classes Therefore we have to import itbefore we use it the first time

522 Creating userMenu View

Next we create the userMenu view which is saved in the file wwwrootblogprotected

componentsviewsuserMenuphp

ltulgt

ltligtltphp echo CHtmllink(rsquoCreate New Postrsquoarray(rsquopostcreatersquo)) gtltligt

ltligtltphp echo CHtmllink(rsquoManage Postsrsquoarray(rsquopostadminrsquo)) gtltligt

ltligtltphp echo CHtmllink(rsquoApprove Commentsrsquoarray(rsquocommentindexrsquo))

rsquo (rsquo Commentmodel()-gtpendingCommentCount rsquo)rsquo gtltligt

ltligtltphp echo CHtmllink(rsquoLogoutrsquoarray(rsquositelogoutrsquo)) gtltligt

ltulgt

Info By default view files for a widget should be placed under the views sub-directory of the directory containing the widget class file The file name must bethe same as the view name

523 Using UserMenu Portlet

It is time for us to make use of our newly completed UserMenu portlet We modify thelayout view file wwwrootblogprotectedviewslayoutscolumn2php as follows

53 Creating Tag Cloud Portlet 43

ltdiv id=sidebargt

ltphp if(Yiiapp()-gtuser-gtisGuest) $this-gtwidget(rsquoUserMenursquo) gt

ltdivgt

In the above we call the widget() method to generate and execute an instance of theUserMenu class Because the portlet should only be displayed to authenticated users weonly call widget() when the isGuest property of the current user is false (meaning theuser is authenticated)

524 Testing UserMenu Portlet

Letrsquos test what we have so far

1 Open a browser window and enter the URL httpwwwexamplecomblogindex

php Verify that there is nothing displayed in the side bar section of the page

2 Click on the Login hyperlink and fill out the login form to login If successful verifythat the UserMenu portlet appears in the side bar and the portlet has the usernameas its title

3 Click on the rsquoLogoutrsquo hyperlink in the UserMenu portlet Verify that the logout actionis successful and the UserMenu portlet disappears

525 Summary

What we have created is a portlet that is highly reusable We can easily reuse it in adifferent project with little or no modification Moreover the design of this portlet followsclosely the philosophy that logic and presentation should be separated While we did notpoint this out in the previous sections such practice is used nearly everywhere in a typicalYii application

53 Creating Tag Cloud Portlet

Tag cloud displays a list of post tags with visual decorations hinting the popularity of eachindividual tag

531 Creating TagCloud Class

We create the TagCloud class in the file wwwrootblogprotectedcomponentsTagCloud

php The file has the following content

44 5 Portlets

Yiiimport(rsquoziiwidgetsCPortletrsquo)

class TagCloud extends CPortlet

public $title=rsquoTagsrsquo

public $maxTags=20

protected function renderContent()

$tags=Tagmodel()-gtfindTagWeights($this-gtmaxTags)

foreach($tags as $tag=gt$weight)

$link=CHtmllink(CHtmlencode($tag) array(rsquopostindexrsquorsquotagrsquo=gt$tag))

echo CHtmltag(rsquospanrsquo array(

rsquoclassrsquo=gtrsquotagrsquo

rsquostylersquo=gtfont-size$weightpt) $link)n

Unlike the UserMenu portlet the TagCloud portlet does not use a view Instead its presen-tation is done in the renderContent() method This is because the presentation does notcontain much HTML tags

We display each tag as a hyperlink to the post index page with the corresponding tagparameter The font size of each tag link is adjusted according to their relative weightamong other tags If a tag has higher frequency value than the other it will have a biggerfont size

532 Using TagCloud Portlet

Usage of the TagCloud portlet is very simple We modify the layout file wwwrootblog

protectedviewslayoutsmainphp as follows

ltdiv id=sidebargt

ltphp if(Yiiapp()-gtuser-gtisGuest) $this-gtwidget(rsquoUserMenursquo) gt

ltphp $this-gtwidget(rsquoTagCloudrsquo array(

rsquomaxTagsrsquo=gtYiiapp()-gtparams[rsquotagCloudCountrsquo]

)) gt

ltdivgt

54 Creating Recent Comments Portlet 45

54 Creating Recent Comments Portlet

In this section we create the last portlet that displays a list of comments recently pub-lished

541 Creating RecentComments Class

We create the RecentComments class in the file wwwrootblogprotectedcomponentsRecentCommentsphp The file has the following content

Yiiimport(rsquoziiwidgetsCPortletrsquo)

class RecentComments extends CPortlet

public $title=rsquoRecent Commentsrsquo

public $maxComments=10

public function getRecentComments()

return Commentmodel()-gtfindRecentComments($this-gtmaxComments)

protected function renderContent()

$this-gtrender(rsquorecentCommentsrsquo)

In the above we invoke the findRecentComments method which is defined in the Comment

class as follows

class Comment extends CActiveRecord

public function findRecentComments($limit=10)

return $this-gtwith(rsquopostrsquo)-gtfindAll(array(

rsquoconditionrsquo=gtrsquotstatus=rsquoselfSTATUS APPROVED

rsquoorderrsquo=gtrsquotcreate time DESCrsquo

rsquolimitrsquo=gt$limit

))

46 5 Portlets

542 Creating recentComments View

The recentComments view is saved in the file wwwrootblogprotectedcomponentsviews

recentCommentsphp It simply displays every comment returned by the RecentComments

getRecentComments() method

543 Using RecentComments Portlet

We modify the layout file wwwrootblogprotectedviewslayoutsmainphp to embedthis last portlet

ltdiv id=sidebargt

ltphp if(Yiiapp()-gtuser-gtisGuest) $this-gtwidget(rsquoUserMenursquo) gt

ltphp $this-gtwidget(rsquoTagCloudrsquo array(

rsquomaxTagsrsquo=gtYiiapp()-gtparams[rsquotagCloudCountrsquo]

)) gt

ltphp $this-gtwidget(rsquoRecentCommentsrsquo array(

rsquomaxCommentsrsquo=gtYiiapp()-gtparams[rsquorecentCommentCountrsquo]

)) gt

ltdivgt

Chapter 6

Final Work

61 Beautifying URLs

The URLs linking various pages of our blog application currently look ugly For examplethe URL for the page showing a post looks like the following

indexphpr=postshowampid=1

In this section we describe how to beautify these URLs and make them SEO-friendlyOur goal is to be able to use the following URLs in the application

1 indexphppostsyii leads to the page showing a list of posts with tag yii

2 indexphppost2A+Test+Post leads to the page showing the detail of the postwith ID 2 whose title is A Test Post

3 indexphppostupdate1 leads to the page that allows updating the post with ID1

Note that in the second URL format we include the post title in the URL This is mainlyto make the URL SEO friendly It is said that search engines may also respect the wordsfound in a URL when it is being indexed

To achieve our goal we modify the application configuration as follows

return array(

rsquocomponentsrsquo=gtarray(

rsquourlManagerrsquo=gtarray(

rsquourlFormatrsquo=gtrsquopathrsquo

rsquorulesrsquo=gtarray(

48 6 Final Work

rsquopostltidd+gtlttitlegtrsquo=gtrsquopostviewrsquorsquopostslttaggtrsquo=gtrsquopostindexrsquo

)

)

)

)

In the above we configure the urlManager component by setting its urlFormat propertyto be path and adding a set of rules

The rules are used by urlManager to parse and create the URLs in the desired formatFor example the first rule says that if a URL indexphppostsyii is requested theurlManager component should be responsible to dispatch the request to the route post

index and generate a tag GET parameter with the value yii On the other hand whencreating a URL with the route postindex and parameter tag the urlManager componentwill also use this rule to generate the desired URL indexphppostsyii For this reasonwe say that urlManager is a two-way URL manager

The urlManager component can further beautify our URLs such as hiding indexphp inthe URLs appending suffix like html to the URLs We can obtain these features easilyby configuring various properties of urlManager in the application configuration For moredetails please refer to the Guide

62 Logging Errors

A production Web application often needs sophisticated logging for various events In ourblog application we would like to log the errors occurring when it is being used Sucherrors could be programming mistakes or usersrsquo misuse of the system Logging these errorswill help us to improve the blog application

We enable the error logging by modifying the application configuration as follows

return array(

rsquopreloadrsquo=gtarray(rsquologrsquo)

rsquocomponentsrsquo=gtarray(

rsquologrsquo=gtarray(

rsquoclassrsquo=gtrsquoCLogRouterrsquo

rsquoroutesrsquo=gtarray(

array(

rsquoclassrsquo=gtrsquoCFileLogRoutersquo

rsquolevelsrsquo=gtrsquoerror warningrsquo

63 Final Tune-up and Deployment 49

)

)

)

)

)

With the above configuration if an error or warning occurs detailed information will belogged and saved in a file located under the directory wwwrootblogprotectedruntime

The log component offers more advanced features such as sending log messages to a listof email addresses displaying log messages in JavaScript console window etc For moredetails please refer to the Guide

63 Final Tune-up and Deployment

We are close to finish our blog application Before deployment we would like to do sometune-ups

631 Changing Home Page

We change to use the post list page as the home page We modify the application config-uration as follows

return array(

rsquodefaultControllerrsquo=gtrsquopostrsquo

)

Tip Because PostController already declares list to be its default action whenwe access the home page of the application we will see the result generated by thelist action of the post controller

632 Enabling Schema Caching

Because ActiveRecord relies on the metadata about tables to determine the column infor-mation it takes time to read the metadata and analyze it This may not be a problemduring development stage but for an application running in production mode it is a totalwaste of time if the database schema does not change Therefore we should enable theschema caching by modifying the application configuration as follows

50 6 Final Work

return array(

rsquocomponentsrsquo=gtarray(

rsquocachersquo=gtarray(

rsquoclassrsquo=gtrsquoCDbCachersquo

)

rsquodbrsquo=gtarray(

rsquoclassrsquo=gtrsquosystemdbCDbConnectionrsquo

rsquoconnectionStringrsquo=gtrsquosqlitewwwrootblogprotecteddatablogdbrsquo

rsquoschemaCachingDurationrsquo=gt3600

)

)

)

In the above we first add a cache component which uses a default SQLite database as thecaching storage If our server is equipped with other caching extensions such as APCwe could change to use them as well We also modify the db component by setting itsschemaCachingDuration property to be 3600 which means the parsed database schemadata can remain valid in cache for 3600 seconds

633 Disabling Debugging Mode

We modify the entry script file wwwrootblogindexphp by removing the line definingthe constant YII DEBUG This constant is useful during development stage because it allowsYii to display more debugging information when an error occurs However when theapplication is running in production mode displaying debugging information is not agood idea because it may contain sensitive information such as where the script file islocated and the content in the file etc

634 Deploying the Application

The final deployment process manly involves copying the directory wwwrootblog to thetarget directory The following checklist shows every needed step

1 Install Yii in the target place if it is not available

2 Copy the entire directory wwwrootblog to the target place

3 Edit the entry script file indexphp by pointing the $yii variable to the new Yiibootstrap file

4 Edit the file protectedyiicphp by setting the $yiic variable to be the new Yiiyiicphp file

64 Future Enhancements 51

5 Change the permission of the directories assets and protectedruntime so that theyare writable by the Web server process

64 Future Enhancements

641 Using a Theme

Without writing any code our blog application is already themeable To use a themewe mainly need to develop the theme by writing customized view files in the theme Forexample to use a theme named classic that uses a different page layout we would createa layout view file wwwrootblogthemesclassicviewslayoutsmainphp We also needto change the application configuration to indicate our choice of the classic theme

return array(

rsquothemersquo=gtrsquoclassicrsquo

)

642 Internationalization

We may also internationalize our blog application so that its pages can be displayed indifferent languages This mainly involves efforts in two aspects

First we may create view files in different languages For example for the list pageof PostController we can create a view file wwwrootblogprotectedviewspostzh cn

listphp When the application is configured to use simplified Chinese (the language codeis zh cn) Yii will automatically use this new view file instead of the original one

Second we may create message translations for those messages generated by code Themessage translations should be saved as files under the directory wwwrootblogprotected

messages We also need to modify the code where we use text strings by enclosing themin the method call Yiit()

For more details about internationalization please refer to the Guide

643 Improving Performance with Cache

While the Yii framework itself is very efficient it is not necessarily true that an applicationwritten in Yii efficient There are several places in our blog application that we can improvethe performance For example the tag clould portlet could be one of the performancebottlenecks because it involves complex database query and PHP logic

52 6 Final Work

We can make use of the sophisticated caching feature provided by Yii to improve theperformance One of the most useful components in Yii is COutputCache which caches afragment of page display so that the underlying code generating the fragment does not needto be executed for every request For example in the layout file wwwrootblogprotected

viewslayoutsmainphp we can enclose the tag cloud portlet with COutputCache

ltphp if($this-gtbeginCache(rsquotagCloudrsquo array(rsquodurationrsquo=gt3600))) gt

ltphp $this-gtwidget(rsquoTagCloudrsquo array(

rsquomaxTagsrsquo=gtYiiapp()-gtparams[rsquotagCloudCountrsquo]

)) gt

ltphp $this-gtendCache() gt

With the above code the tag cloud display will be served from cache instead of beinggenerated on-the-fly for every request The cached content will remain valid in cache for3600 seconds

644 Adding New Features

Our blog application only has very basic functionalities To become a complete blogsystem more features are needed for example calendar portlet email notifications postcategorization archived post portlet and so on We will leave the implementation of thesefeatures to interested readers

  • Contents
  • License
  • Getting Started
    • Building a Blog System using Yii
    • Testdriving with Yii
      • Installing Yii
      • Creating Skeleton Application
      • Application Workflow
        • Requirements Analysis
        • Overall Design
          • Initial Prototyping
            • Setting Up Database
              • Creating Database
              • Establishing Database Connection
                • Scaffolding
                • Authenticating User
                • Summary
                  • Post Management
                    • Customizing Post Model
                      • Customizing rules() Method
                      • Customizing relations() Method
                      • Representing Status in Text
                        • Creating and Updating Posts
                          • Customizing Access Control
                          • Customizing create and update Operations
                            • Displaying Posts
                              • Customizing view Operation
                              • Customizing index Operation
                                • Managing Posts
                                  • Listing Posts in Tabular View
                                  • Deleting Posts
                                      • Comment Management
                                        • Customizing Comment Model
                                          • Customizing rules() Method
                                          • Customizing attributeLabels() Method
                                          • Customizing Saving Process
                                            • Creating and Displaying Comments
                                              • Displaying Comments
                                              • Creating Comments
                                                • Managing Comments
                                                  • Updating and Deleting Comments
                                                  • Approving Comments
                                                      • Portlets
                                                        • Customizing Page Layout
                                                        • Creating User Menu Portlet
                                                          • Creating UserMenu Class
                                                          • Creating userMenu View
                                                          • Using UserMenu Portlet
                                                          • Testing UserMenu Portlet
                                                          • Summary
                                                            • Creating Tag Cloud Portlet
                                                              • Creating TagCloud Class
                                                              • Using TagCloud Portlet
                                                                • Creating Recent Comments Portlet
                                                                  • Creating RecentComments Class
                                                                  • Creating recentComments View
                                                                  • Using RecentComments Portlet
                                                                      • Final Work
                                                                        • Beautifying URLs
                                                                        • Logging Errors
                                                                        • Final Tune-up and Deployment
                                                                          • Changing Home Page
                                                                          • Enabling Schema Caching
                                                                          • Disabling Debugging Mode
                                                                          • Deploying the Application
                                                                            • Future Enhancements
                                                                              • Using a Theme
                                                                              • Internationalization
                                                                              • Improving Performance with Cache
                                                                              • Adding New Features
Page 33: Yii Blog 1.1.0 Demo Tutor

26 3 Post Management

$this-gtrender(rsquoviewrsquoarray(

$post=$this-gtloadModel()

$this-gtrender(rsquoviewrsquoarray(

rsquomodelrsquo=gt$post

))

))

private $ model

public function loadModel()

if($this-gt model===null)

if(isset($ GET[rsquoidrsquo]))

if(Yiiapp()-gtuser-gtisGuest)

$condition=rsquostatus=rsquoPostSTATUS PUBLISHED

rsquo OR status=rsquoPOSTSTATUS ARCHIVED

else

$condition=rsquorsquo

$this-gt model=Postmodel()-gtfindbyPk($ GET[rsquoidrsquo] $condition)

if($this-gt model===null)

throw new CHttpException(404rsquoThe requested page does not existrsquo)

return $this-gt model

Our change mainly lies in the loadModel() method In this method we query the Post

table according to the id GET parameter If the post is not found or if it is not publishedor archived (when the user is a guest) we will throw a 404 HTTP error Otherwise thepost object is returned to actionView() which in turn passes the post object to the viewscript for further display

Tip Yii captures HTTP exceptions (instances of CHttpException) and displaysthem in either predefined templates or customized error views The skeleton appli-cation generated by yiic already contains a customized error view in wwwroot

blogprotectedviewssiteerrorphp We can modify this file if we want tofurther customize the error display

The change in the view script is mainly about ajdusting the formatting and styles of thepost display We will not go into details here Interested readers may refer to wwwroot

blogprotectedviewspostviewphp

33 Displaying Posts 27

332 Customizing index Operation

Like the view operation we customize the index operation in two places the actionIndex()

method in PostController and the view file wwwrootblogprotectedviewspostindex

php We mainly need to add the support for displaying a list of posts that are associatedwith a specified tag

Below is the modified actionIndex() method in PostController

public function actionIndex()

$criteria=new CDbCriteria(array(

rsquoconditionrsquo=gtrsquostatus=rsquoPostSTATUS PUBLISHED

rsquoorderrsquo=gtrsquoupdate time DESCrsquo

rsquowithrsquo=gtrsquocommentCountrsquo

))

if(isset($ GET[rsquotagrsquo]))

$criteria-gtaddSearchCondition(rsquotagsrsquo$ GET[rsquotagrsquo])

$dataProvider=new CActiveDataProvider(rsquoPostrsquo array(

rsquopaginationrsquo=gtarray(

rsquopageSizersquo=gtYiiapp()-gtparams[rsquopostsPerPagersquo]

)

rsquocriteriarsquo=gt$criteria

))

$this-gtrender(rsquoindexrsquoarray(

rsquodataProviderrsquo=gt$dataProvider

))

In the above we first create a query criteria for retrieving post list The criteria statesthat only published posts should be returned and they should be sorted according to theirupdate time in descending order Because when displaying a post in the list we want toshow how many comments the post has received in the criteria we also specify to bringback commentCount which if you remember is a relation declared in Postrelations()

In case when a user wants to see posts with a specific tag we would add a search conditionto the criteria to look for the specified tag

Using the query criteria we create a data provider which mainly serves for three purposesFirst it does pagination of the data when too many results may be returned Herewe customize the pagination by setting the page size to be an application parameterpostsPerPage Second it does sorting according to the user request And finally it feedsthe paginated and sorted data to widgets or view code for presentation

28 3 Post Management

After we finish with actionIndex() we modify the index view as follows Our change ismainly about adding the h1 header when the user specifies to display posts with a tag

ltphp if(empty($ GET[rsquotagrsquo])) gt

lth1gtPosts Tagged with ltigtltphp echo CHtmlencode($ GET[rsquotagrsquo]) gtltigtlth1gt

ltphp endif gt

ltphp $this-gtwidget(rsquoziiwidgetsCListViewrsquo array(

rsquodataProviderrsquo=gt$dataProvider

rsquoitemViewrsquo=gtrsquo viewrsquo

rsquotemplatersquo=gtitemsnpager)) gt

Note that in the above we use [CListView] to display the post list This widget requires apartial view to display the detail of each individual post Here we specify the partial viewto be view which means the file wwwrootblogprotectedviewspost viewphp In thisview script we can acccess the post instance being displayed via a local variable named$data

34 Managing Posts

Managing posts mainly refers to listing posts in an administrative view that allows us tosee posts with all statuses updating them and deleting them They are accomplished bythe admin operation and the delete operation respectively The code generated by yiic

does not need much modification Below we mainly explain how these two operations areimplemented

341 Listing Posts in Tabular View

The admin operation shows posts with all statuses in a tabular view The view supportssorting and pagination The following is the actionAdmin() method in PostController

public function actionAdmin()

$dataProvider=new CActiveDataProvider(rsquoPostrsquo array(

rsquosortrsquo=gtarray(

rsquodefaultOrderrsquo=gtrsquostatus update time DESCrsquo

)

))

$this-gtrender(rsquoadminrsquoarray(

rsquodataProviderrsquo=gt$dataProvider

))

34 Managing Posts 29

The above code is very similar to that in actionIndex() The main difference is in thequery criteria Here we do not specify any particular query criteria since we want to seeall posts We do customize the sort property of the data provider so that by default theposts are shown in the order of their statuses and update time

Below is the code for the admin view

lth1gtManage Postslth1gt

ltphp $this-gtwidget(rsquoziiwidgetsgridCGridViewrsquo array(

rsquodataProviderrsquo=gt$dataProvider

rsquocolumnsrsquo=gtarray(

array(

rsquonamersquo=gtrsquotitlersquo

rsquotypersquo=gtrsquorawrsquo

rsquovaluersquo=gtrsquoCHtmllink(CHtmlencode($data-gttitle) $data-gturl)rsquo

)

array(

rsquonamersquo=gtrsquostatusrsquo

rsquovaluersquo=gtrsquoLookupitem(PostStatus$data-gtstatus)rsquo

)

rsquocreate timedatetimersquo

rsquoupdate timedatetimersquo

array(

rsquoclassrsquo=gtrsquoCButtonColumnrsquo

)

)

)) gt

We use [CGridView] to display the posts It allows us to sort by a column and paginatethrough the posts if there are too many to be displayed in a single page Our change ismainly about how to display each column For example for the title column we specifythat it should be displayed as a hyperlink that points to the detailed view of the post

Tip When displaying text we call CHtmlencode() to encode HTML entities init This prevents from cross-site scripting attack

342 Deleting Posts

In the admin data grid there is a delete button in each row Clicking on the button shoulddelete the corresponding post Internally this triggers the delete action implemented asfollows

30 3 Post Management

public function actionDelete()

if(Yiiapp()-gtrequest-gtisPostRequest)

we only allow deletion via POST request

$this-gtloadModel()-gtdelete()

if(isset($ POST[rsquoajaxrsquo]))

$this-gtredirect(array(rsquoindexrsquo))

else

throw new CHttpException(400rsquoInvalid request Please do not repeat this request againrsquo)

The above code is the one generated by the yiic tool without any change We wouldlike to explain a little bit more about the checking on $ POST[rsquoajaxrsquo] The [CGridView]widget has a very nice feature that its sorting pagination and deletion operations are alldone in AJAX mode by default That means the whole page does not get reloaded if anyof the above operations is performed However it is also possible that the widget runs innon-AJAX mode (by setting its ajaxUpdate property to be false or disabling JavaScript onthe client side) It is necessary for the delete action to differentiate these two scenarios ifthe delete request is made via AJAX we should not redirect the user browser otherwisewe should

Deleting a post should also cause the deletion of all comments for that post In additionwe should also update the tbl tag table regarding the tags for the deleted post Both ofthese tasks can be achieved by writing an afterDelete method in the Post model class asfollows

protected function afterDelete()

parentafterDelete()

Commentmodel()-gtdeleteAll(rsquopost id=rsquo$this-gtid)

Tagmodel()-gtupdateFrequency($this-gttags rsquorsquo)

The above code is very straightforward it first deletes all those comments whose post id

is the same as the ID of the deleted post it then updates the tbl tag table for the tags

of the deleted post

34 Managing Posts 31

Tip We have to explicitly delete all comments for the deleted post here becauseSQLite does not really support foreign key constraints In a DBMS that supportsthis constraint (such as MySQL PostgreSQL) the foreign key constraint can be setup such that the DBMS automatically deletes the related comments if the post isdeleted In that case we no longer this explicit deletion call in our code

32 3 Post Management

Chapter 4

Comment Management

41 Customizing Comment Model

For the Comment model we mainly need to customize the rules() and attributeLabels()

methods The attributeLabels() method returns a mapping between attribute namesand attribute labels We do not need to touch relations() since the code generated bythe yiic tool is good enough

411 Customizing rules() Method

We first customize the validation rules generated by the yiic tool The following rules areused for comments

public function rules()

return array(

array(rsquocontent author emailrsquo rsquorequiredrsquo)

array(rsquoauthor email urlrsquo rsquolengthrsquo rsquomaxrsquo=gt128)

array(rsquoemailrsquorsquoemailrsquo)

array(rsquourlrsquorsquourlrsquo)

)

In the above we specify that the author email and content attributes are required thelength of author email and url cannot exceed 128 the email attribute must be a validemail address and the url attribute must be a valid URL

412 Customizing attributeLabels() Method

We then customize the attributeLabels() method to declare the label display for eachmodel attribute This method returns an array consisting of name-label pairs When wecall CHtmlactiveLabel() to display an attribute label

34 4 Comment Management

public function attributeLabels()

return array(

rsquoidrsquo =gt rsquoIdrsquo

rsquocontentrsquo =gt rsquoCommentrsquo

rsquostatusrsquo =gt rsquoStatusrsquo

rsquocreate timersquo =gt rsquoCreate Timersquo

rsquoauthorrsquo =gt rsquoNamersquo

rsquoemailrsquo =gt rsquoEmailrsquo

rsquourlrsquo =gt rsquoWebsitersquo

rsquopost idrsquo =gt rsquoPostrsquo

)

Tip If the label for an attribute is not declared in attributeLabels() an algo-rithm will be used to generate an appropriate label For example a label VerifyCode will be generated for attributes verify code or verifyCode

413 Customizing Saving Process

Because we want to record the creation time of a comment we override the beforeSave()

method of Comment like we do for the Post model

protected function beforeSave()

if(parentbeforeSave())

if($this-gtisNewRecord)

$this-gtcreate time=time()

return true

else

return false

42 Creating and Displaying Comments

In this section we implement the comment display and creation features

421 Displaying Comments

Instead of displaying and creating comments on individual pages we use the post detailpage (generated by the view action of PostController) Below the post content display

42 Creating and Displaying Comments 35

we display first a list of comments belonging to that post and then a comment creationform

In order to display comments on the post detail page we modify the view script wwwrootblogprotectedviewspostviewphp as follows

ltdiv id=commentsgt

ltphp if($model-gtcommentCountgt=1) gt

lth3gt

ltphp echo $model-gtcommentCount rsquocomment(s)rsquo gt

lth3gt

ltphp $this-gtrenderPartial(rsquo commentsrsquoarray(

rsquopostrsquo=gt$model

rsquocommentsrsquo=gt$model-gtcomments

)) gt

ltphp endif gt

ltdivgt

In the above we call renderPartial() to render a partial view named comments to displaythe list of comments belonging to the current post Note that in the view we use theexpression $model-gtcomments to retrieve the comments for the post This is valid becausewe have declared a comments relation in the Post class Evaluating this expression wouldtrigger an implicit JOIN database query to bring back the proper comments This featureis known as lazy relational query

The partial view comments is not very interesting It mainly goes through every commentand displays the detail of it Interested readers may refer to wwwrootyiidemosblog

protectedpost commentsphp

422 Creating Comments

To handle comment creation we first modify the actionView() method of PostController

as follows

public function actionView()

$post=$this-gtloadModel()

$comment=$this-gtnewComment($post)

$this-gtrender(rsquoviewrsquoarray(

rsquomodelrsquo=gt$post

rsquocommentrsquo=gt$comment

))

36 4 Comment Management

protected function newComment($post)

$comment=new Comment

if(isset($ POST[rsquoCommentrsquo]))

$comment-gtattributes=$ POST[rsquoCommentrsquo]

if($post-gtaddComment($comment))

if($comment-gtstatus==CommentSTATUS PENDING)

Yiiapp()-gtuser-gtsetFlash(rsquocommentSubmittedrsquorsquoThank yoursquo)

$this-gtrefresh()

return $comment

In the above we call the newComment() method before we render view In the newComment()

method we generate a Comment instance and check if the comment form is submitted If sowe try to add the comment for the post by calling $post-gtaddComment($comment) If it goesthrough we refresh the post detail page In case the comment needs to be approved we willshow a flash message to indicate this decision A flash message is usually a confirmationmessage displayed to end users If the user clicks on the refresh button of his browser themessage will disappear

We also need to modify wwwrootblogprotectedviewspostviewphp furthermore

ltdiv id=commentsgt

lth3gtLeave a Commentlth3gt

ltphp if(Yiiapp()-gtuser-gthasFlash(rsquocommentSubmittedrsquo)) gt

ltdiv class=successgt

ltphp echo Yiiapp()-gtuser-gtgetFlash(rsquocommentSubmittedrsquo) gt

ltdivgt

ltphp else gt

ltphp $this-gtrenderPartial(rsquocomment formrsquoarray(

rsquomodelrsquo=gt$comment

)) gt

ltphp endif gt

ltdivgtlt-- comments --gt

43 Managing Comments 37

In the above code we display the flash message if it is available If not we displaythe comment input form by rendering the partial view wwwrootblogprotectedviews

comment formphp

43 Managing Comments

Comment management includes updating deleting and approving comments These op-erations are implemented as actions in the CommentController class

431 Updating and Deleting Comments

The code generated by yiic for updating and deleting comments remains largely un-changed

432 Approving Comments

When comments are newly created they are in pending approval status and need to beapproved in order to be visible to guest users Approving a comment is mainly aboutchanging the status column of the comment

We create an actionApprove() method in CommentController as follows

public function actionApprove()

if(Yiiapp()-gtrequest-gtisPostRequest)

$comment=$this-gtloadModel()

$comment-gtapprove()

$this-gtredirect(array(rsquoindexrsquo))

else

throw new CHttpException(400rsquoInvalid requestrsquo)

In the above when the approve action is invoked via a POST request we call the approve()

method defined in the Comment model to change the status We then redirect the userbrowser to the page displaying the post that this comment belongs to

We also modify the actionIndex() method of Comment to show all comments We wouldlike to see comments pending approved to show up first

public function actionIndex()

38 4 Comment Management

$dataProvider=new CActiveDataProvider(rsquoCommentrsquo array(

rsquocriteriarsquo=gtarray(

rsquowithrsquo=gtrsquopostrsquo

rsquoorderrsquo=gtrsquotstatus tcreate time DESCrsquo

)

))

$this-gtrender(rsquoindexrsquoarray(

rsquodataProviderrsquo=gt$dataProvider

))

Notice that in the above code because both tbl post and tbl comment have columns statusand create time we need to disambiguate the corresponding column reference by prefixingthem with table alias names As described in the guide the alias for the primary table ina relational query is always t Therefore we are prefixing t to the status and create time

columns in the above code

Like the post index view the index view for CommentController uses [CListView] to displaythe comment list which in turn uses the partial view wwwrootblogprotectedviews

comment viewphp to display the detail of each individual comment We will not go intodetails here Interested readers may refer to the corresponding file in the blog demowwwrootyiidemosblogprotectedviewscomment viewphp

Chapter 5

Portlets

51 Customizing Page Layout

Features like rdquothe most recent commentsrdquo rdquotag cloudrdquo are better to be implemented inportlets A portlet is a pluggable user interface component that renders a fragment ofHTML code In this section we describe how to set up the portlet architecture for ourblog application

Before we go into details about developing portlets we first need to adjust our page layoutso that the portlets can be placed in a side bar

A page layout determines how to share common sections (eg header footer) amongdifferent pages and how to embed contents that are specific for different pages In Yiia page layout is represented as a view under the protectedviewslayouts folder Whenwe call the render() method in a controller the rendering result will be automaticallyembedded into this layout view before it is displayed to end users The default layoutview is named as main which means the wwwrootblogprotectedviewslayoutsmain

php file in our blog application

We could directly modify the main layout to add a side bar However doing so wouldmake some irrelevant pages such as about contact pages to have a side bar which is notalways desirable For this reason we need different layouts for different pages

We thus create two different layouts column1 and column2 The former displays the pagein a single column while the latter displays two columns in the page one of which is theside bar We will use column2 layout for the post and comment pages and column1 forthe rest of the pages Because both column1 and column2 still share the same header andfooter sections we let them to share the main layout

Based on the above description we create the column1 layout as follows

ltphp $this-gtbeginContent(rsquolayoutsmainrsquo) gt

ltdiv class=containergt

40 5 Portlets

ltdiv id=contentgt

ltphp echo $content gt

ltdivgtlt-- content --gt

ltdivgt

ltphp $this-gtendContent() gt

and the column2 layout as follows

ltphp $this-gtbeginContent(rsquolayoutsmainrsquo) gt

ltdiv class=containergt

ltdiv class=span-18gt

ltdiv id=contentgt

ltphp echo $content gt

ltdivgtlt-- content --gt

ltdivgt

ltdiv class=span-6 lastgt

ltdiv id=sidebargt

portlets to be placed here

ltdivgtlt-- sidebar --gt

ltdivgt

ltdivgt

ltphp $this-gtendContent() gt

As we can see both column1 and column2 are enclosed in a pair of beginContent andendContent calls They essentially use the specified view layoutsmain to decorate theenclosed content In all these layout views the variable $content refers to the dynamiccontent to be embedded into the layout

Besides customizing the layout view file we also need to adjust the CSS file wwwroot

blogcssmaincss so that the overall appearance would look like what we see in the blogdemo We will not go into details here

Info The skeleton application generated by the yiic tool uses the BluePrint CSSframework It provides a nice font reset and a robust grid system For more detailson how to use this framework please refer to its documentation

After creating the new layouts we need to set the layout property of controllers to makeuse of them So far we have three controllers SiteController PostController andCommentController We set SiteController to use column1 as the default layout whilethe rest two of the controllers use column2 for example

class PostController extends Controller

52 Creating User Menu Portlet 41

public $layout=rsquocolumn2rsquo

Tip If some view of the controller does not use the same layout as declaredby the layout property it may customize it in the view by a simple assignment$this-gtlayout=rsquonewlayoutrsquo

52 Creating User Menu Portlet

Based on the requirements analysis we need three different portlets the rdquouser menurdquoportlet the rdquotag cloudrdquo portlet and the rdquorecent commentsrdquo portlet We will develop theseportlets by extending the [CPortlet] widget provided by Yii

In this section we will develop our first concrete portlet - the user menu portlet whichdisplays a list of menu items that are only available to authenticated users The menucontains four items

bull Approve Comments a hyperlink that leads to a list of comments pending approval

bull Create New Post a hyperlink that leads to the post creation page

bull Manage Posts a hyperlink that leads to the post management page

bull Logout a link button that would log out the current user

521 Creating UserMenu Class

We create the UserMenu class to represent the logic part of the user menu portlet Theclass is saved in the file wwwrootblogprotectedcomponentsUserMenuphp which has thefollowing content

Yiiimport(rsquoziiwidgetsCPortletrsquo)

class UserMenu extends CPortlet

public function init()

$this-gttitle=CHtmlencode(Yiiapp()-gtuser-gtname)

parentinit()

42 5 Portlets

protected function renderContent()

$this-gtrender(rsquouserMenursquo)

The UserMenu class extends from the CPortlet class that we created previously It overridesboth the init() method and the renderContent() method of CPortlet The former setsthe portlet title to be the name of the current user the latter generates the portlet bodycontent by rendering a view named userMenu

Tip Notice that we have to explicitly include the CPortlet class by calling Yii

import() before we refer to it the first time This is because CPortlet is part of thezii project ndash the official extension library for Yii For performance considerationclasses in this project are not listed as core classes Therefore we have to import itbefore we use it the first time

522 Creating userMenu View

Next we create the userMenu view which is saved in the file wwwrootblogprotected

componentsviewsuserMenuphp

ltulgt

ltligtltphp echo CHtmllink(rsquoCreate New Postrsquoarray(rsquopostcreatersquo)) gtltligt

ltligtltphp echo CHtmllink(rsquoManage Postsrsquoarray(rsquopostadminrsquo)) gtltligt

ltligtltphp echo CHtmllink(rsquoApprove Commentsrsquoarray(rsquocommentindexrsquo))

rsquo (rsquo Commentmodel()-gtpendingCommentCount rsquo)rsquo gtltligt

ltligtltphp echo CHtmllink(rsquoLogoutrsquoarray(rsquositelogoutrsquo)) gtltligt

ltulgt

Info By default view files for a widget should be placed under the views sub-directory of the directory containing the widget class file The file name must bethe same as the view name

523 Using UserMenu Portlet

It is time for us to make use of our newly completed UserMenu portlet We modify thelayout view file wwwrootblogprotectedviewslayoutscolumn2php as follows

53 Creating Tag Cloud Portlet 43

ltdiv id=sidebargt

ltphp if(Yiiapp()-gtuser-gtisGuest) $this-gtwidget(rsquoUserMenursquo) gt

ltdivgt

In the above we call the widget() method to generate and execute an instance of theUserMenu class Because the portlet should only be displayed to authenticated users weonly call widget() when the isGuest property of the current user is false (meaning theuser is authenticated)

524 Testing UserMenu Portlet

Letrsquos test what we have so far

1 Open a browser window and enter the URL httpwwwexamplecomblogindex

php Verify that there is nothing displayed in the side bar section of the page

2 Click on the Login hyperlink and fill out the login form to login If successful verifythat the UserMenu portlet appears in the side bar and the portlet has the usernameas its title

3 Click on the rsquoLogoutrsquo hyperlink in the UserMenu portlet Verify that the logout actionis successful and the UserMenu portlet disappears

525 Summary

What we have created is a portlet that is highly reusable We can easily reuse it in adifferent project with little or no modification Moreover the design of this portlet followsclosely the philosophy that logic and presentation should be separated While we did notpoint this out in the previous sections such practice is used nearly everywhere in a typicalYii application

53 Creating Tag Cloud Portlet

Tag cloud displays a list of post tags with visual decorations hinting the popularity of eachindividual tag

531 Creating TagCloud Class

We create the TagCloud class in the file wwwrootblogprotectedcomponentsTagCloud

php The file has the following content

44 5 Portlets

Yiiimport(rsquoziiwidgetsCPortletrsquo)

class TagCloud extends CPortlet

public $title=rsquoTagsrsquo

public $maxTags=20

protected function renderContent()

$tags=Tagmodel()-gtfindTagWeights($this-gtmaxTags)

foreach($tags as $tag=gt$weight)

$link=CHtmllink(CHtmlencode($tag) array(rsquopostindexrsquorsquotagrsquo=gt$tag))

echo CHtmltag(rsquospanrsquo array(

rsquoclassrsquo=gtrsquotagrsquo

rsquostylersquo=gtfont-size$weightpt) $link)n

Unlike the UserMenu portlet the TagCloud portlet does not use a view Instead its presen-tation is done in the renderContent() method This is because the presentation does notcontain much HTML tags

We display each tag as a hyperlink to the post index page with the corresponding tagparameter The font size of each tag link is adjusted according to their relative weightamong other tags If a tag has higher frequency value than the other it will have a biggerfont size

532 Using TagCloud Portlet

Usage of the TagCloud portlet is very simple We modify the layout file wwwrootblog

protectedviewslayoutsmainphp as follows

ltdiv id=sidebargt

ltphp if(Yiiapp()-gtuser-gtisGuest) $this-gtwidget(rsquoUserMenursquo) gt

ltphp $this-gtwidget(rsquoTagCloudrsquo array(

rsquomaxTagsrsquo=gtYiiapp()-gtparams[rsquotagCloudCountrsquo]

)) gt

ltdivgt

54 Creating Recent Comments Portlet 45

54 Creating Recent Comments Portlet

In this section we create the last portlet that displays a list of comments recently pub-lished

541 Creating RecentComments Class

We create the RecentComments class in the file wwwrootblogprotectedcomponentsRecentCommentsphp The file has the following content

Yiiimport(rsquoziiwidgetsCPortletrsquo)

class RecentComments extends CPortlet

public $title=rsquoRecent Commentsrsquo

public $maxComments=10

public function getRecentComments()

return Commentmodel()-gtfindRecentComments($this-gtmaxComments)

protected function renderContent()

$this-gtrender(rsquorecentCommentsrsquo)

In the above we invoke the findRecentComments method which is defined in the Comment

class as follows

class Comment extends CActiveRecord

public function findRecentComments($limit=10)

return $this-gtwith(rsquopostrsquo)-gtfindAll(array(

rsquoconditionrsquo=gtrsquotstatus=rsquoselfSTATUS APPROVED

rsquoorderrsquo=gtrsquotcreate time DESCrsquo

rsquolimitrsquo=gt$limit

))

46 5 Portlets

542 Creating recentComments View

The recentComments view is saved in the file wwwrootblogprotectedcomponentsviews

recentCommentsphp It simply displays every comment returned by the RecentComments

getRecentComments() method

543 Using RecentComments Portlet

We modify the layout file wwwrootblogprotectedviewslayoutsmainphp to embedthis last portlet

ltdiv id=sidebargt

ltphp if(Yiiapp()-gtuser-gtisGuest) $this-gtwidget(rsquoUserMenursquo) gt

ltphp $this-gtwidget(rsquoTagCloudrsquo array(

rsquomaxTagsrsquo=gtYiiapp()-gtparams[rsquotagCloudCountrsquo]

)) gt

ltphp $this-gtwidget(rsquoRecentCommentsrsquo array(

rsquomaxCommentsrsquo=gtYiiapp()-gtparams[rsquorecentCommentCountrsquo]

)) gt

ltdivgt

Chapter 6

Final Work

61 Beautifying URLs

The URLs linking various pages of our blog application currently look ugly For examplethe URL for the page showing a post looks like the following

indexphpr=postshowampid=1

In this section we describe how to beautify these URLs and make them SEO-friendlyOur goal is to be able to use the following URLs in the application

1 indexphppostsyii leads to the page showing a list of posts with tag yii

2 indexphppost2A+Test+Post leads to the page showing the detail of the postwith ID 2 whose title is A Test Post

3 indexphppostupdate1 leads to the page that allows updating the post with ID1

Note that in the second URL format we include the post title in the URL This is mainlyto make the URL SEO friendly It is said that search engines may also respect the wordsfound in a URL when it is being indexed

To achieve our goal we modify the application configuration as follows

return array(

rsquocomponentsrsquo=gtarray(

rsquourlManagerrsquo=gtarray(

rsquourlFormatrsquo=gtrsquopathrsquo

rsquorulesrsquo=gtarray(

48 6 Final Work

rsquopostltidd+gtlttitlegtrsquo=gtrsquopostviewrsquorsquopostslttaggtrsquo=gtrsquopostindexrsquo

)

)

)

)

In the above we configure the urlManager component by setting its urlFormat propertyto be path and adding a set of rules

The rules are used by urlManager to parse and create the URLs in the desired formatFor example the first rule says that if a URL indexphppostsyii is requested theurlManager component should be responsible to dispatch the request to the route post

index and generate a tag GET parameter with the value yii On the other hand whencreating a URL with the route postindex and parameter tag the urlManager componentwill also use this rule to generate the desired URL indexphppostsyii For this reasonwe say that urlManager is a two-way URL manager

The urlManager component can further beautify our URLs such as hiding indexphp inthe URLs appending suffix like html to the URLs We can obtain these features easilyby configuring various properties of urlManager in the application configuration For moredetails please refer to the Guide

62 Logging Errors

A production Web application often needs sophisticated logging for various events In ourblog application we would like to log the errors occurring when it is being used Sucherrors could be programming mistakes or usersrsquo misuse of the system Logging these errorswill help us to improve the blog application

We enable the error logging by modifying the application configuration as follows

return array(

rsquopreloadrsquo=gtarray(rsquologrsquo)

rsquocomponentsrsquo=gtarray(

rsquologrsquo=gtarray(

rsquoclassrsquo=gtrsquoCLogRouterrsquo

rsquoroutesrsquo=gtarray(

array(

rsquoclassrsquo=gtrsquoCFileLogRoutersquo

rsquolevelsrsquo=gtrsquoerror warningrsquo

63 Final Tune-up and Deployment 49

)

)

)

)

)

With the above configuration if an error or warning occurs detailed information will belogged and saved in a file located under the directory wwwrootblogprotectedruntime

The log component offers more advanced features such as sending log messages to a listof email addresses displaying log messages in JavaScript console window etc For moredetails please refer to the Guide

63 Final Tune-up and Deployment

We are close to finish our blog application Before deployment we would like to do sometune-ups

631 Changing Home Page

We change to use the post list page as the home page We modify the application config-uration as follows

return array(

rsquodefaultControllerrsquo=gtrsquopostrsquo

)

Tip Because PostController already declares list to be its default action whenwe access the home page of the application we will see the result generated by thelist action of the post controller

632 Enabling Schema Caching

Because ActiveRecord relies on the metadata about tables to determine the column infor-mation it takes time to read the metadata and analyze it This may not be a problemduring development stage but for an application running in production mode it is a totalwaste of time if the database schema does not change Therefore we should enable theschema caching by modifying the application configuration as follows

50 6 Final Work

return array(

rsquocomponentsrsquo=gtarray(

rsquocachersquo=gtarray(

rsquoclassrsquo=gtrsquoCDbCachersquo

)

rsquodbrsquo=gtarray(

rsquoclassrsquo=gtrsquosystemdbCDbConnectionrsquo

rsquoconnectionStringrsquo=gtrsquosqlitewwwrootblogprotecteddatablogdbrsquo

rsquoschemaCachingDurationrsquo=gt3600

)

)

)

In the above we first add a cache component which uses a default SQLite database as thecaching storage If our server is equipped with other caching extensions such as APCwe could change to use them as well We also modify the db component by setting itsschemaCachingDuration property to be 3600 which means the parsed database schemadata can remain valid in cache for 3600 seconds

633 Disabling Debugging Mode

We modify the entry script file wwwrootblogindexphp by removing the line definingthe constant YII DEBUG This constant is useful during development stage because it allowsYii to display more debugging information when an error occurs However when theapplication is running in production mode displaying debugging information is not agood idea because it may contain sensitive information such as where the script file islocated and the content in the file etc

634 Deploying the Application

The final deployment process manly involves copying the directory wwwrootblog to thetarget directory The following checklist shows every needed step

1 Install Yii in the target place if it is not available

2 Copy the entire directory wwwrootblog to the target place

3 Edit the entry script file indexphp by pointing the $yii variable to the new Yiibootstrap file

4 Edit the file protectedyiicphp by setting the $yiic variable to be the new Yiiyiicphp file

64 Future Enhancements 51

5 Change the permission of the directories assets and protectedruntime so that theyare writable by the Web server process

64 Future Enhancements

641 Using a Theme

Without writing any code our blog application is already themeable To use a themewe mainly need to develop the theme by writing customized view files in the theme Forexample to use a theme named classic that uses a different page layout we would createa layout view file wwwrootblogthemesclassicviewslayoutsmainphp We also needto change the application configuration to indicate our choice of the classic theme

return array(

rsquothemersquo=gtrsquoclassicrsquo

)

642 Internationalization

We may also internationalize our blog application so that its pages can be displayed indifferent languages This mainly involves efforts in two aspects

First we may create view files in different languages For example for the list pageof PostController we can create a view file wwwrootblogprotectedviewspostzh cn

listphp When the application is configured to use simplified Chinese (the language codeis zh cn) Yii will automatically use this new view file instead of the original one

Second we may create message translations for those messages generated by code Themessage translations should be saved as files under the directory wwwrootblogprotected

messages We also need to modify the code where we use text strings by enclosing themin the method call Yiit()

For more details about internationalization please refer to the Guide

643 Improving Performance with Cache

While the Yii framework itself is very efficient it is not necessarily true that an applicationwritten in Yii efficient There are several places in our blog application that we can improvethe performance For example the tag clould portlet could be one of the performancebottlenecks because it involves complex database query and PHP logic

52 6 Final Work

We can make use of the sophisticated caching feature provided by Yii to improve theperformance One of the most useful components in Yii is COutputCache which caches afragment of page display so that the underlying code generating the fragment does not needto be executed for every request For example in the layout file wwwrootblogprotected

viewslayoutsmainphp we can enclose the tag cloud portlet with COutputCache

ltphp if($this-gtbeginCache(rsquotagCloudrsquo array(rsquodurationrsquo=gt3600))) gt

ltphp $this-gtwidget(rsquoTagCloudrsquo array(

rsquomaxTagsrsquo=gtYiiapp()-gtparams[rsquotagCloudCountrsquo]

)) gt

ltphp $this-gtendCache() gt

With the above code the tag cloud display will be served from cache instead of beinggenerated on-the-fly for every request The cached content will remain valid in cache for3600 seconds

644 Adding New Features

Our blog application only has very basic functionalities To become a complete blogsystem more features are needed for example calendar portlet email notifications postcategorization archived post portlet and so on We will leave the implementation of thesefeatures to interested readers

  • Contents
  • License
  • Getting Started
    • Building a Blog System using Yii
    • Testdriving with Yii
      • Installing Yii
      • Creating Skeleton Application
      • Application Workflow
        • Requirements Analysis
        • Overall Design
          • Initial Prototyping
            • Setting Up Database
              • Creating Database
              • Establishing Database Connection
                • Scaffolding
                • Authenticating User
                • Summary
                  • Post Management
                    • Customizing Post Model
                      • Customizing rules() Method
                      • Customizing relations() Method
                      • Representing Status in Text
                        • Creating and Updating Posts
                          • Customizing Access Control
                          • Customizing create and update Operations
                            • Displaying Posts
                              • Customizing view Operation
                              • Customizing index Operation
                                • Managing Posts
                                  • Listing Posts in Tabular View
                                  • Deleting Posts
                                      • Comment Management
                                        • Customizing Comment Model
                                          • Customizing rules() Method
                                          • Customizing attributeLabels() Method
                                          • Customizing Saving Process
                                            • Creating and Displaying Comments
                                              • Displaying Comments
                                              • Creating Comments
                                                • Managing Comments
                                                  • Updating and Deleting Comments
                                                  • Approving Comments
                                                      • Portlets
                                                        • Customizing Page Layout
                                                        • Creating User Menu Portlet
                                                          • Creating UserMenu Class
                                                          • Creating userMenu View
                                                          • Using UserMenu Portlet
                                                          • Testing UserMenu Portlet
                                                          • Summary
                                                            • Creating Tag Cloud Portlet
                                                              • Creating TagCloud Class
                                                              • Using TagCloud Portlet
                                                                • Creating Recent Comments Portlet
                                                                  • Creating RecentComments Class
                                                                  • Creating recentComments View
                                                                  • Using RecentComments Portlet
                                                                      • Final Work
                                                                        • Beautifying URLs
                                                                        • Logging Errors
                                                                        • Final Tune-up and Deployment
                                                                          • Changing Home Page
                                                                          • Enabling Schema Caching
                                                                          • Disabling Debugging Mode
                                                                          • Deploying the Application
                                                                            • Future Enhancements
                                                                              • Using a Theme
                                                                              • Internationalization
                                                                              • Improving Performance with Cache
                                                                              • Adding New Features
Page 34: Yii Blog 1.1.0 Demo Tutor

33 Displaying Posts 27

332 Customizing index Operation

Like the view operation we customize the index operation in two places the actionIndex()

method in PostController and the view file wwwrootblogprotectedviewspostindex

php We mainly need to add the support for displaying a list of posts that are associatedwith a specified tag

Below is the modified actionIndex() method in PostController

public function actionIndex()

$criteria=new CDbCriteria(array(

rsquoconditionrsquo=gtrsquostatus=rsquoPostSTATUS PUBLISHED

rsquoorderrsquo=gtrsquoupdate time DESCrsquo

rsquowithrsquo=gtrsquocommentCountrsquo

))

if(isset($ GET[rsquotagrsquo]))

$criteria-gtaddSearchCondition(rsquotagsrsquo$ GET[rsquotagrsquo])

$dataProvider=new CActiveDataProvider(rsquoPostrsquo array(

rsquopaginationrsquo=gtarray(

rsquopageSizersquo=gtYiiapp()-gtparams[rsquopostsPerPagersquo]

)

rsquocriteriarsquo=gt$criteria

))

$this-gtrender(rsquoindexrsquoarray(

rsquodataProviderrsquo=gt$dataProvider

))

In the above we first create a query criteria for retrieving post list The criteria statesthat only published posts should be returned and they should be sorted according to theirupdate time in descending order Because when displaying a post in the list we want toshow how many comments the post has received in the criteria we also specify to bringback commentCount which if you remember is a relation declared in Postrelations()

In case when a user wants to see posts with a specific tag we would add a search conditionto the criteria to look for the specified tag

Using the query criteria we create a data provider which mainly serves for three purposesFirst it does pagination of the data when too many results may be returned Herewe customize the pagination by setting the page size to be an application parameterpostsPerPage Second it does sorting according to the user request And finally it feedsthe paginated and sorted data to widgets or view code for presentation

28 3 Post Management

After we finish with actionIndex() we modify the index view as follows Our change ismainly about adding the h1 header when the user specifies to display posts with a tag

ltphp if(empty($ GET[rsquotagrsquo])) gt

lth1gtPosts Tagged with ltigtltphp echo CHtmlencode($ GET[rsquotagrsquo]) gtltigtlth1gt

ltphp endif gt

ltphp $this-gtwidget(rsquoziiwidgetsCListViewrsquo array(

rsquodataProviderrsquo=gt$dataProvider

rsquoitemViewrsquo=gtrsquo viewrsquo

rsquotemplatersquo=gtitemsnpager)) gt

Note that in the above we use [CListView] to display the post list This widget requires apartial view to display the detail of each individual post Here we specify the partial viewto be view which means the file wwwrootblogprotectedviewspost viewphp In thisview script we can acccess the post instance being displayed via a local variable named$data

34 Managing Posts

Managing posts mainly refers to listing posts in an administrative view that allows us tosee posts with all statuses updating them and deleting them They are accomplished bythe admin operation and the delete operation respectively The code generated by yiic

does not need much modification Below we mainly explain how these two operations areimplemented

341 Listing Posts in Tabular View

The admin operation shows posts with all statuses in a tabular view The view supportssorting and pagination The following is the actionAdmin() method in PostController

public function actionAdmin()

$dataProvider=new CActiveDataProvider(rsquoPostrsquo array(

rsquosortrsquo=gtarray(

rsquodefaultOrderrsquo=gtrsquostatus update time DESCrsquo

)

))

$this-gtrender(rsquoadminrsquoarray(

rsquodataProviderrsquo=gt$dataProvider

))

34 Managing Posts 29

The above code is very similar to that in actionIndex() The main difference is in thequery criteria Here we do not specify any particular query criteria since we want to seeall posts We do customize the sort property of the data provider so that by default theposts are shown in the order of their statuses and update time

Below is the code for the admin view

lth1gtManage Postslth1gt

ltphp $this-gtwidget(rsquoziiwidgetsgridCGridViewrsquo array(

rsquodataProviderrsquo=gt$dataProvider

rsquocolumnsrsquo=gtarray(

array(

rsquonamersquo=gtrsquotitlersquo

rsquotypersquo=gtrsquorawrsquo

rsquovaluersquo=gtrsquoCHtmllink(CHtmlencode($data-gttitle) $data-gturl)rsquo

)

array(

rsquonamersquo=gtrsquostatusrsquo

rsquovaluersquo=gtrsquoLookupitem(PostStatus$data-gtstatus)rsquo

)

rsquocreate timedatetimersquo

rsquoupdate timedatetimersquo

array(

rsquoclassrsquo=gtrsquoCButtonColumnrsquo

)

)

)) gt

We use [CGridView] to display the posts It allows us to sort by a column and paginatethrough the posts if there are too many to be displayed in a single page Our change ismainly about how to display each column For example for the title column we specifythat it should be displayed as a hyperlink that points to the detailed view of the post

Tip When displaying text we call CHtmlencode() to encode HTML entities init This prevents from cross-site scripting attack

342 Deleting Posts

In the admin data grid there is a delete button in each row Clicking on the button shoulddelete the corresponding post Internally this triggers the delete action implemented asfollows

30 3 Post Management

public function actionDelete()

if(Yiiapp()-gtrequest-gtisPostRequest)

we only allow deletion via POST request

$this-gtloadModel()-gtdelete()

if(isset($ POST[rsquoajaxrsquo]))

$this-gtredirect(array(rsquoindexrsquo))

else

throw new CHttpException(400rsquoInvalid request Please do not repeat this request againrsquo)

The above code is the one generated by the yiic tool without any change We wouldlike to explain a little bit more about the checking on $ POST[rsquoajaxrsquo] The [CGridView]widget has a very nice feature that its sorting pagination and deletion operations are alldone in AJAX mode by default That means the whole page does not get reloaded if anyof the above operations is performed However it is also possible that the widget runs innon-AJAX mode (by setting its ajaxUpdate property to be false or disabling JavaScript onthe client side) It is necessary for the delete action to differentiate these two scenarios ifthe delete request is made via AJAX we should not redirect the user browser otherwisewe should

Deleting a post should also cause the deletion of all comments for that post In additionwe should also update the tbl tag table regarding the tags for the deleted post Both ofthese tasks can be achieved by writing an afterDelete method in the Post model class asfollows

protected function afterDelete()

parentafterDelete()

Commentmodel()-gtdeleteAll(rsquopost id=rsquo$this-gtid)

Tagmodel()-gtupdateFrequency($this-gttags rsquorsquo)

The above code is very straightforward it first deletes all those comments whose post id

is the same as the ID of the deleted post it then updates the tbl tag table for the tags

of the deleted post

34 Managing Posts 31

Tip We have to explicitly delete all comments for the deleted post here becauseSQLite does not really support foreign key constraints In a DBMS that supportsthis constraint (such as MySQL PostgreSQL) the foreign key constraint can be setup such that the DBMS automatically deletes the related comments if the post isdeleted In that case we no longer this explicit deletion call in our code

32 3 Post Management

Chapter 4

Comment Management

41 Customizing Comment Model

For the Comment model we mainly need to customize the rules() and attributeLabels()

methods The attributeLabels() method returns a mapping between attribute namesand attribute labels We do not need to touch relations() since the code generated bythe yiic tool is good enough

411 Customizing rules() Method

We first customize the validation rules generated by the yiic tool The following rules areused for comments

public function rules()

return array(

array(rsquocontent author emailrsquo rsquorequiredrsquo)

array(rsquoauthor email urlrsquo rsquolengthrsquo rsquomaxrsquo=gt128)

array(rsquoemailrsquorsquoemailrsquo)

array(rsquourlrsquorsquourlrsquo)

)

In the above we specify that the author email and content attributes are required thelength of author email and url cannot exceed 128 the email attribute must be a validemail address and the url attribute must be a valid URL

412 Customizing attributeLabels() Method

We then customize the attributeLabels() method to declare the label display for eachmodel attribute This method returns an array consisting of name-label pairs When wecall CHtmlactiveLabel() to display an attribute label

34 4 Comment Management

public function attributeLabels()

return array(

rsquoidrsquo =gt rsquoIdrsquo

rsquocontentrsquo =gt rsquoCommentrsquo

rsquostatusrsquo =gt rsquoStatusrsquo

rsquocreate timersquo =gt rsquoCreate Timersquo

rsquoauthorrsquo =gt rsquoNamersquo

rsquoemailrsquo =gt rsquoEmailrsquo

rsquourlrsquo =gt rsquoWebsitersquo

rsquopost idrsquo =gt rsquoPostrsquo

)

Tip If the label for an attribute is not declared in attributeLabels() an algo-rithm will be used to generate an appropriate label For example a label VerifyCode will be generated for attributes verify code or verifyCode

413 Customizing Saving Process

Because we want to record the creation time of a comment we override the beforeSave()

method of Comment like we do for the Post model

protected function beforeSave()

if(parentbeforeSave())

if($this-gtisNewRecord)

$this-gtcreate time=time()

return true

else

return false

42 Creating and Displaying Comments

In this section we implement the comment display and creation features

421 Displaying Comments

Instead of displaying and creating comments on individual pages we use the post detailpage (generated by the view action of PostController) Below the post content display

42 Creating and Displaying Comments 35

we display first a list of comments belonging to that post and then a comment creationform

In order to display comments on the post detail page we modify the view script wwwrootblogprotectedviewspostviewphp as follows

ltdiv id=commentsgt

ltphp if($model-gtcommentCountgt=1) gt

lth3gt

ltphp echo $model-gtcommentCount rsquocomment(s)rsquo gt

lth3gt

ltphp $this-gtrenderPartial(rsquo commentsrsquoarray(

rsquopostrsquo=gt$model

rsquocommentsrsquo=gt$model-gtcomments

)) gt

ltphp endif gt

ltdivgt

In the above we call renderPartial() to render a partial view named comments to displaythe list of comments belonging to the current post Note that in the view we use theexpression $model-gtcomments to retrieve the comments for the post This is valid becausewe have declared a comments relation in the Post class Evaluating this expression wouldtrigger an implicit JOIN database query to bring back the proper comments This featureis known as lazy relational query

The partial view comments is not very interesting It mainly goes through every commentand displays the detail of it Interested readers may refer to wwwrootyiidemosblog

protectedpost commentsphp

422 Creating Comments

To handle comment creation we first modify the actionView() method of PostController

as follows

public function actionView()

$post=$this-gtloadModel()

$comment=$this-gtnewComment($post)

$this-gtrender(rsquoviewrsquoarray(

rsquomodelrsquo=gt$post

rsquocommentrsquo=gt$comment

))

36 4 Comment Management

protected function newComment($post)

$comment=new Comment

if(isset($ POST[rsquoCommentrsquo]))

$comment-gtattributes=$ POST[rsquoCommentrsquo]

if($post-gtaddComment($comment))

if($comment-gtstatus==CommentSTATUS PENDING)

Yiiapp()-gtuser-gtsetFlash(rsquocommentSubmittedrsquorsquoThank yoursquo)

$this-gtrefresh()

return $comment

In the above we call the newComment() method before we render view In the newComment()

method we generate a Comment instance and check if the comment form is submitted If sowe try to add the comment for the post by calling $post-gtaddComment($comment) If it goesthrough we refresh the post detail page In case the comment needs to be approved we willshow a flash message to indicate this decision A flash message is usually a confirmationmessage displayed to end users If the user clicks on the refresh button of his browser themessage will disappear

We also need to modify wwwrootblogprotectedviewspostviewphp furthermore

ltdiv id=commentsgt

lth3gtLeave a Commentlth3gt

ltphp if(Yiiapp()-gtuser-gthasFlash(rsquocommentSubmittedrsquo)) gt

ltdiv class=successgt

ltphp echo Yiiapp()-gtuser-gtgetFlash(rsquocommentSubmittedrsquo) gt

ltdivgt

ltphp else gt

ltphp $this-gtrenderPartial(rsquocomment formrsquoarray(

rsquomodelrsquo=gt$comment

)) gt

ltphp endif gt

ltdivgtlt-- comments --gt

43 Managing Comments 37

In the above code we display the flash message if it is available If not we displaythe comment input form by rendering the partial view wwwrootblogprotectedviews

comment formphp

43 Managing Comments

Comment management includes updating deleting and approving comments These op-erations are implemented as actions in the CommentController class

431 Updating and Deleting Comments

The code generated by yiic for updating and deleting comments remains largely un-changed

432 Approving Comments

When comments are newly created they are in pending approval status and need to beapproved in order to be visible to guest users Approving a comment is mainly aboutchanging the status column of the comment

We create an actionApprove() method in CommentController as follows

public function actionApprove()

if(Yiiapp()-gtrequest-gtisPostRequest)

$comment=$this-gtloadModel()

$comment-gtapprove()

$this-gtredirect(array(rsquoindexrsquo))

else

throw new CHttpException(400rsquoInvalid requestrsquo)

In the above when the approve action is invoked via a POST request we call the approve()

method defined in the Comment model to change the status We then redirect the userbrowser to the page displaying the post that this comment belongs to

We also modify the actionIndex() method of Comment to show all comments We wouldlike to see comments pending approved to show up first

public function actionIndex()

38 4 Comment Management

$dataProvider=new CActiveDataProvider(rsquoCommentrsquo array(

rsquocriteriarsquo=gtarray(

rsquowithrsquo=gtrsquopostrsquo

rsquoorderrsquo=gtrsquotstatus tcreate time DESCrsquo

)

))

$this-gtrender(rsquoindexrsquoarray(

rsquodataProviderrsquo=gt$dataProvider

))

Notice that in the above code because both tbl post and tbl comment have columns statusand create time we need to disambiguate the corresponding column reference by prefixingthem with table alias names As described in the guide the alias for the primary table ina relational query is always t Therefore we are prefixing t to the status and create time

columns in the above code

Like the post index view the index view for CommentController uses [CListView] to displaythe comment list which in turn uses the partial view wwwrootblogprotectedviews

comment viewphp to display the detail of each individual comment We will not go intodetails here Interested readers may refer to the corresponding file in the blog demowwwrootyiidemosblogprotectedviewscomment viewphp

Chapter 5

Portlets

51 Customizing Page Layout

Features like rdquothe most recent commentsrdquo rdquotag cloudrdquo are better to be implemented inportlets A portlet is a pluggable user interface component that renders a fragment ofHTML code In this section we describe how to set up the portlet architecture for ourblog application

Before we go into details about developing portlets we first need to adjust our page layoutso that the portlets can be placed in a side bar

A page layout determines how to share common sections (eg header footer) amongdifferent pages and how to embed contents that are specific for different pages In Yiia page layout is represented as a view under the protectedviewslayouts folder Whenwe call the render() method in a controller the rendering result will be automaticallyembedded into this layout view before it is displayed to end users The default layoutview is named as main which means the wwwrootblogprotectedviewslayoutsmain

php file in our blog application

We could directly modify the main layout to add a side bar However doing so wouldmake some irrelevant pages such as about contact pages to have a side bar which is notalways desirable For this reason we need different layouts for different pages

We thus create two different layouts column1 and column2 The former displays the pagein a single column while the latter displays two columns in the page one of which is theside bar We will use column2 layout for the post and comment pages and column1 forthe rest of the pages Because both column1 and column2 still share the same header andfooter sections we let them to share the main layout

Based on the above description we create the column1 layout as follows

ltphp $this-gtbeginContent(rsquolayoutsmainrsquo) gt

ltdiv class=containergt

40 5 Portlets

ltdiv id=contentgt

ltphp echo $content gt

ltdivgtlt-- content --gt

ltdivgt

ltphp $this-gtendContent() gt

and the column2 layout as follows

ltphp $this-gtbeginContent(rsquolayoutsmainrsquo) gt

ltdiv class=containergt

ltdiv class=span-18gt

ltdiv id=contentgt

ltphp echo $content gt

ltdivgtlt-- content --gt

ltdivgt

ltdiv class=span-6 lastgt

ltdiv id=sidebargt

portlets to be placed here

ltdivgtlt-- sidebar --gt

ltdivgt

ltdivgt

ltphp $this-gtendContent() gt

As we can see both column1 and column2 are enclosed in a pair of beginContent andendContent calls They essentially use the specified view layoutsmain to decorate theenclosed content In all these layout views the variable $content refers to the dynamiccontent to be embedded into the layout

Besides customizing the layout view file we also need to adjust the CSS file wwwroot

blogcssmaincss so that the overall appearance would look like what we see in the blogdemo We will not go into details here

Info The skeleton application generated by the yiic tool uses the BluePrint CSSframework It provides a nice font reset and a robust grid system For more detailson how to use this framework please refer to its documentation

After creating the new layouts we need to set the layout property of controllers to makeuse of them So far we have three controllers SiteController PostController andCommentController We set SiteController to use column1 as the default layout whilethe rest two of the controllers use column2 for example

class PostController extends Controller

52 Creating User Menu Portlet 41

public $layout=rsquocolumn2rsquo

Tip If some view of the controller does not use the same layout as declaredby the layout property it may customize it in the view by a simple assignment$this-gtlayout=rsquonewlayoutrsquo

52 Creating User Menu Portlet

Based on the requirements analysis we need three different portlets the rdquouser menurdquoportlet the rdquotag cloudrdquo portlet and the rdquorecent commentsrdquo portlet We will develop theseportlets by extending the [CPortlet] widget provided by Yii

In this section we will develop our first concrete portlet - the user menu portlet whichdisplays a list of menu items that are only available to authenticated users The menucontains four items

bull Approve Comments a hyperlink that leads to a list of comments pending approval

bull Create New Post a hyperlink that leads to the post creation page

bull Manage Posts a hyperlink that leads to the post management page

bull Logout a link button that would log out the current user

521 Creating UserMenu Class

We create the UserMenu class to represent the logic part of the user menu portlet Theclass is saved in the file wwwrootblogprotectedcomponentsUserMenuphp which has thefollowing content

Yiiimport(rsquoziiwidgetsCPortletrsquo)

class UserMenu extends CPortlet

public function init()

$this-gttitle=CHtmlencode(Yiiapp()-gtuser-gtname)

parentinit()

42 5 Portlets

protected function renderContent()

$this-gtrender(rsquouserMenursquo)

The UserMenu class extends from the CPortlet class that we created previously It overridesboth the init() method and the renderContent() method of CPortlet The former setsthe portlet title to be the name of the current user the latter generates the portlet bodycontent by rendering a view named userMenu

Tip Notice that we have to explicitly include the CPortlet class by calling Yii

import() before we refer to it the first time This is because CPortlet is part of thezii project ndash the official extension library for Yii For performance considerationclasses in this project are not listed as core classes Therefore we have to import itbefore we use it the first time

522 Creating userMenu View

Next we create the userMenu view which is saved in the file wwwrootblogprotected

componentsviewsuserMenuphp

ltulgt

ltligtltphp echo CHtmllink(rsquoCreate New Postrsquoarray(rsquopostcreatersquo)) gtltligt

ltligtltphp echo CHtmllink(rsquoManage Postsrsquoarray(rsquopostadminrsquo)) gtltligt

ltligtltphp echo CHtmllink(rsquoApprove Commentsrsquoarray(rsquocommentindexrsquo))

rsquo (rsquo Commentmodel()-gtpendingCommentCount rsquo)rsquo gtltligt

ltligtltphp echo CHtmllink(rsquoLogoutrsquoarray(rsquositelogoutrsquo)) gtltligt

ltulgt

Info By default view files for a widget should be placed under the views sub-directory of the directory containing the widget class file The file name must bethe same as the view name

523 Using UserMenu Portlet

It is time for us to make use of our newly completed UserMenu portlet We modify thelayout view file wwwrootblogprotectedviewslayoutscolumn2php as follows

53 Creating Tag Cloud Portlet 43

ltdiv id=sidebargt

ltphp if(Yiiapp()-gtuser-gtisGuest) $this-gtwidget(rsquoUserMenursquo) gt

ltdivgt

In the above we call the widget() method to generate and execute an instance of theUserMenu class Because the portlet should only be displayed to authenticated users weonly call widget() when the isGuest property of the current user is false (meaning theuser is authenticated)

524 Testing UserMenu Portlet

Letrsquos test what we have so far

1 Open a browser window and enter the URL httpwwwexamplecomblogindex

php Verify that there is nothing displayed in the side bar section of the page

2 Click on the Login hyperlink and fill out the login form to login If successful verifythat the UserMenu portlet appears in the side bar and the portlet has the usernameas its title

3 Click on the rsquoLogoutrsquo hyperlink in the UserMenu portlet Verify that the logout actionis successful and the UserMenu portlet disappears

525 Summary

What we have created is a portlet that is highly reusable We can easily reuse it in adifferent project with little or no modification Moreover the design of this portlet followsclosely the philosophy that logic and presentation should be separated While we did notpoint this out in the previous sections such practice is used nearly everywhere in a typicalYii application

53 Creating Tag Cloud Portlet

Tag cloud displays a list of post tags with visual decorations hinting the popularity of eachindividual tag

531 Creating TagCloud Class

We create the TagCloud class in the file wwwrootblogprotectedcomponentsTagCloud

php The file has the following content

44 5 Portlets

Yiiimport(rsquoziiwidgetsCPortletrsquo)

class TagCloud extends CPortlet

public $title=rsquoTagsrsquo

public $maxTags=20

protected function renderContent()

$tags=Tagmodel()-gtfindTagWeights($this-gtmaxTags)

foreach($tags as $tag=gt$weight)

$link=CHtmllink(CHtmlencode($tag) array(rsquopostindexrsquorsquotagrsquo=gt$tag))

echo CHtmltag(rsquospanrsquo array(

rsquoclassrsquo=gtrsquotagrsquo

rsquostylersquo=gtfont-size$weightpt) $link)n

Unlike the UserMenu portlet the TagCloud portlet does not use a view Instead its presen-tation is done in the renderContent() method This is because the presentation does notcontain much HTML tags

We display each tag as a hyperlink to the post index page with the corresponding tagparameter The font size of each tag link is adjusted according to their relative weightamong other tags If a tag has higher frequency value than the other it will have a biggerfont size

532 Using TagCloud Portlet

Usage of the TagCloud portlet is very simple We modify the layout file wwwrootblog

protectedviewslayoutsmainphp as follows

ltdiv id=sidebargt

ltphp if(Yiiapp()-gtuser-gtisGuest) $this-gtwidget(rsquoUserMenursquo) gt

ltphp $this-gtwidget(rsquoTagCloudrsquo array(

rsquomaxTagsrsquo=gtYiiapp()-gtparams[rsquotagCloudCountrsquo]

)) gt

ltdivgt

54 Creating Recent Comments Portlet 45

54 Creating Recent Comments Portlet

In this section we create the last portlet that displays a list of comments recently pub-lished

541 Creating RecentComments Class

We create the RecentComments class in the file wwwrootblogprotectedcomponentsRecentCommentsphp The file has the following content

Yiiimport(rsquoziiwidgetsCPortletrsquo)

class RecentComments extends CPortlet

public $title=rsquoRecent Commentsrsquo

public $maxComments=10

public function getRecentComments()

return Commentmodel()-gtfindRecentComments($this-gtmaxComments)

protected function renderContent()

$this-gtrender(rsquorecentCommentsrsquo)

In the above we invoke the findRecentComments method which is defined in the Comment

class as follows

class Comment extends CActiveRecord

public function findRecentComments($limit=10)

return $this-gtwith(rsquopostrsquo)-gtfindAll(array(

rsquoconditionrsquo=gtrsquotstatus=rsquoselfSTATUS APPROVED

rsquoorderrsquo=gtrsquotcreate time DESCrsquo

rsquolimitrsquo=gt$limit

))

46 5 Portlets

542 Creating recentComments View

The recentComments view is saved in the file wwwrootblogprotectedcomponentsviews

recentCommentsphp It simply displays every comment returned by the RecentComments

getRecentComments() method

543 Using RecentComments Portlet

We modify the layout file wwwrootblogprotectedviewslayoutsmainphp to embedthis last portlet

ltdiv id=sidebargt

ltphp if(Yiiapp()-gtuser-gtisGuest) $this-gtwidget(rsquoUserMenursquo) gt

ltphp $this-gtwidget(rsquoTagCloudrsquo array(

rsquomaxTagsrsquo=gtYiiapp()-gtparams[rsquotagCloudCountrsquo]

)) gt

ltphp $this-gtwidget(rsquoRecentCommentsrsquo array(

rsquomaxCommentsrsquo=gtYiiapp()-gtparams[rsquorecentCommentCountrsquo]

)) gt

ltdivgt

Chapter 6

Final Work

61 Beautifying URLs

The URLs linking various pages of our blog application currently look ugly For examplethe URL for the page showing a post looks like the following

indexphpr=postshowampid=1

In this section we describe how to beautify these URLs and make them SEO-friendlyOur goal is to be able to use the following URLs in the application

1 indexphppostsyii leads to the page showing a list of posts with tag yii

2 indexphppost2A+Test+Post leads to the page showing the detail of the postwith ID 2 whose title is A Test Post

3 indexphppostupdate1 leads to the page that allows updating the post with ID1

Note that in the second URL format we include the post title in the URL This is mainlyto make the URL SEO friendly It is said that search engines may also respect the wordsfound in a URL when it is being indexed

To achieve our goal we modify the application configuration as follows

return array(

rsquocomponentsrsquo=gtarray(

rsquourlManagerrsquo=gtarray(

rsquourlFormatrsquo=gtrsquopathrsquo

rsquorulesrsquo=gtarray(

48 6 Final Work

rsquopostltidd+gtlttitlegtrsquo=gtrsquopostviewrsquorsquopostslttaggtrsquo=gtrsquopostindexrsquo

)

)

)

)

In the above we configure the urlManager component by setting its urlFormat propertyto be path and adding a set of rules

The rules are used by urlManager to parse and create the URLs in the desired formatFor example the first rule says that if a URL indexphppostsyii is requested theurlManager component should be responsible to dispatch the request to the route post

index and generate a tag GET parameter with the value yii On the other hand whencreating a URL with the route postindex and parameter tag the urlManager componentwill also use this rule to generate the desired URL indexphppostsyii For this reasonwe say that urlManager is a two-way URL manager

The urlManager component can further beautify our URLs such as hiding indexphp inthe URLs appending suffix like html to the URLs We can obtain these features easilyby configuring various properties of urlManager in the application configuration For moredetails please refer to the Guide

62 Logging Errors

A production Web application often needs sophisticated logging for various events In ourblog application we would like to log the errors occurring when it is being used Sucherrors could be programming mistakes or usersrsquo misuse of the system Logging these errorswill help us to improve the blog application

We enable the error logging by modifying the application configuration as follows

return array(

rsquopreloadrsquo=gtarray(rsquologrsquo)

rsquocomponentsrsquo=gtarray(

rsquologrsquo=gtarray(

rsquoclassrsquo=gtrsquoCLogRouterrsquo

rsquoroutesrsquo=gtarray(

array(

rsquoclassrsquo=gtrsquoCFileLogRoutersquo

rsquolevelsrsquo=gtrsquoerror warningrsquo

63 Final Tune-up and Deployment 49

)

)

)

)

)

With the above configuration if an error or warning occurs detailed information will belogged and saved in a file located under the directory wwwrootblogprotectedruntime

The log component offers more advanced features such as sending log messages to a listof email addresses displaying log messages in JavaScript console window etc For moredetails please refer to the Guide

63 Final Tune-up and Deployment

We are close to finish our blog application Before deployment we would like to do sometune-ups

631 Changing Home Page

We change to use the post list page as the home page We modify the application config-uration as follows

return array(

rsquodefaultControllerrsquo=gtrsquopostrsquo

)

Tip Because PostController already declares list to be its default action whenwe access the home page of the application we will see the result generated by thelist action of the post controller

632 Enabling Schema Caching

Because ActiveRecord relies on the metadata about tables to determine the column infor-mation it takes time to read the metadata and analyze it This may not be a problemduring development stage but for an application running in production mode it is a totalwaste of time if the database schema does not change Therefore we should enable theschema caching by modifying the application configuration as follows

50 6 Final Work

return array(

rsquocomponentsrsquo=gtarray(

rsquocachersquo=gtarray(

rsquoclassrsquo=gtrsquoCDbCachersquo

)

rsquodbrsquo=gtarray(

rsquoclassrsquo=gtrsquosystemdbCDbConnectionrsquo

rsquoconnectionStringrsquo=gtrsquosqlitewwwrootblogprotecteddatablogdbrsquo

rsquoschemaCachingDurationrsquo=gt3600

)

)

)

In the above we first add a cache component which uses a default SQLite database as thecaching storage If our server is equipped with other caching extensions such as APCwe could change to use them as well We also modify the db component by setting itsschemaCachingDuration property to be 3600 which means the parsed database schemadata can remain valid in cache for 3600 seconds

633 Disabling Debugging Mode

We modify the entry script file wwwrootblogindexphp by removing the line definingthe constant YII DEBUG This constant is useful during development stage because it allowsYii to display more debugging information when an error occurs However when theapplication is running in production mode displaying debugging information is not agood idea because it may contain sensitive information such as where the script file islocated and the content in the file etc

634 Deploying the Application

The final deployment process manly involves copying the directory wwwrootblog to thetarget directory The following checklist shows every needed step

1 Install Yii in the target place if it is not available

2 Copy the entire directory wwwrootblog to the target place

3 Edit the entry script file indexphp by pointing the $yii variable to the new Yiibootstrap file

4 Edit the file protectedyiicphp by setting the $yiic variable to be the new Yiiyiicphp file

64 Future Enhancements 51

5 Change the permission of the directories assets and protectedruntime so that theyare writable by the Web server process

64 Future Enhancements

641 Using a Theme

Without writing any code our blog application is already themeable To use a themewe mainly need to develop the theme by writing customized view files in the theme Forexample to use a theme named classic that uses a different page layout we would createa layout view file wwwrootblogthemesclassicviewslayoutsmainphp We also needto change the application configuration to indicate our choice of the classic theme

return array(

rsquothemersquo=gtrsquoclassicrsquo

)

642 Internationalization

We may also internationalize our blog application so that its pages can be displayed indifferent languages This mainly involves efforts in two aspects

First we may create view files in different languages For example for the list pageof PostController we can create a view file wwwrootblogprotectedviewspostzh cn

listphp When the application is configured to use simplified Chinese (the language codeis zh cn) Yii will automatically use this new view file instead of the original one

Second we may create message translations for those messages generated by code Themessage translations should be saved as files under the directory wwwrootblogprotected

messages We also need to modify the code where we use text strings by enclosing themin the method call Yiit()

For more details about internationalization please refer to the Guide

643 Improving Performance with Cache

While the Yii framework itself is very efficient it is not necessarily true that an applicationwritten in Yii efficient There are several places in our blog application that we can improvethe performance For example the tag clould portlet could be one of the performancebottlenecks because it involves complex database query and PHP logic

52 6 Final Work

We can make use of the sophisticated caching feature provided by Yii to improve theperformance One of the most useful components in Yii is COutputCache which caches afragment of page display so that the underlying code generating the fragment does not needto be executed for every request For example in the layout file wwwrootblogprotected

viewslayoutsmainphp we can enclose the tag cloud portlet with COutputCache

ltphp if($this-gtbeginCache(rsquotagCloudrsquo array(rsquodurationrsquo=gt3600))) gt

ltphp $this-gtwidget(rsquoTagCloudrsquo array(

rsquomaxTagsrsquo=gtYiiapp()-gtparams[rsquotagCloudCountrsquo]

)) gt

ltphp $this-gtendCache() gt

With the above code the tag cloud display will be served from cache instead of beinggenerated on-the-fly for every request The cached content will remain valid in cache for3600 seconds

644 Adding New Features

Our blog application only has very basic functionalities To become a complete blogsystem more features are needed for example calendar portlet email notifications postcategorization archived post portlet and so on We will leave the implementation of thesefeatures to interested readers

  • Contents
  • License
  • Getting Started
    • Building a Blog System using Yii
    • Testdriving with Yii
      • Installing Yii
      • Creating Skeleton Application
      • Application Workflow
        • Requirements Analysis
        • Overall Design
          • Initial Prototyping
            • Setting Up Database
              • Creating Database
              • Establishing Database Connection
                • Scaffolding
                • Authenticating User
                • Summary
                  • Post Management
                    • Customizing Post Model
                      • Customizing rules() Method
                      • Customizing relations() Method
                      • Representing Status in Text
                        • Creating and Updating Posts
                          • Customizing Access Control
                          • Customizing create and update Operations
                            • Displaying Posts
                              • Customizing view Operation
                              • Customizing index Operation
                                • Managing Posts
                                  • Listing Posts in Tabular View
                                  • Deleting Posts
                                      • Comment Management
                                        • Customizing Comment Model
                                          • Customizing rules() Method
                                          • Customizing attributeLabels() Method
                                          • Customizing Saving Process
                                            • Creating and Displaying Comments
                                              • Displaying Comments
                                              • Creating Comments
                                                • Managing Comments
                                                  • Updating and Deleting Comments
                                                  • Approving Comments
                                                      • Portlets
                                                        • Customizing Page Layout
                                                        • Creating User Menu Portlet
                                                          • Creating UserMenu Class
                                                          • Creating userMenu View
                                                          • Using UserMenu Portlet
                                                          • Testing UserMenu Portlet
                                                          • Summary
                                                            • Creating Tag Cloud Portlet
                                                              • Creating TagCloud Class
                                                              • Using TagCloud Portlet
                                                                • Creating Recent Comments Portlet
                                                                  • Creating RecentComments Class
                                                                  • Creating recentComments View
                                                                  • Using RecentComments Portlet
                                                                      • Final Work
                                                                        • Beautifying URLs
                                                                        • Logging Errors
                                                                        • Final Tune-up and Deployment
                                                                          • Changing Home Page
                                                                          • Enabling Schema Caching
                                                                          • Disabling Debugging Mode
                                                                          • Deploying the Application
                                                                            • Future Enhancements
                                                                              • Using a Theme
                                                                              • Internationalization
                                                                              • Improving Performance with Cache
                                                                              • Adding New Features
Page 35: Yii Blog 1.1.0 Demo Tutor

28 3 Post Management

After we finish with actionIndex() we modify the index view as follows Our change ismainly about adding the h1 header when the user specifies to display posts with a tag

ltphp if(empty($ GET[rsquotagrsquo])) gt

lth1gtPosts Tagged with ltigtltphp echo CHtmlencode($ GET[rsquotagrsquo]) gtltigtlth1gt

ltphp endif gt

ltphp $this-gtwidget(rsquoziiwidgetsCListViewrsquo array(

rsquodataProviderrsquo=gt$dataProvider

rsquoitemViewrsquo=gtrsquo viewrsquo

rsquotemplatersquo=gtitemsnpager)) gt

Note that in the above we use [CListView] to display the post list This widget requires apartial view to display the detail of each individual post Here we specify the partial viewto be view which means the file wwwrootblogprotectedviewspost viewphp In thisview script we can acccess the post instance being displayed via a local variable named$data

34 Managing Posts

Managing posts mainly refers to listing posts in an administrative view that allows us tosee posts with all statuses updating them and deleting them They are accomplished bythe admin operation and the delete operation respectively The code generated by yiic

does not need much modification Below we mainly explain how these two operations areimplemented

341 Listing Posts in Tabular View

The admin operation shows posts with all statuses in a tabular view The view supportssorting and pagination The following is the actionAdmin() method in PostController

public function actionAdmin()

$dataProvider=new CActiveDataProvider(rsquoPostrsquo array(

rsquosortrsquo=gtarray(

rsquodefaultOrderrsquo=gtrsquostatus update time DESCrsquo

)

))

$this-gtrender(rsquoadminrsquoarray(

rsquodataProviderrsquo=gt$dataProvider

))

34 Managing Posts 29

The above code is very similar to that in actionIndex() The main difference is in thequery criteria Here we do not specify any particular query criteria since we want to seeall posts We do customize the sort property of the data provider so that by default theposts are shown in the order of their statuses and update time

Below is the code for the admin view

lth1gtManage Postslth1gt

ltphp $this-gtwidget(rsquoziiwidgetsgridCGridViewrsquo array(

rsquodataProviderrsquo=gt$dataProvider

rsquocolumnsrsquo=gtarray(

array(

rsquonamersquo=gtrsquotitlersquo

rsquotypersquo=gtrsquorawrsquo

rsquovaluersquo=gtrsquoCHtmllink(CHtmlencode($data-gttitle) $data-gturl)rsquo

)

array(

rsquonamersquo=gtrsquostatusrsquo

rsquovaluersquo=gtrsquoLookupitem(PostStatus$data-gtstatus)rsquo

)

rsquocreate timedatetimersquo

rsquoupdate timedatetimersquo

array(

rsquoclassrsquo=gtrsquoCButtonColumnrsquo

)

)

)) gt

We use [CGridView] to display the posts It allows us to sort by a column and paginatethrough the posts if there are too many to be displayed in a single page Our change ismainly about how to display each column For example for the title column we specifythat it should be displayed as a hyperlink that points to the detailed view of the post

Tip When displaying text we call CHtmlencode() to encode HTML entities init This prevents from cross-site scripting attack

342 Deleting Posts

In the admin data grid there is a delete button in each row Clicking on the button shoulddelete the corresponding post Internally this triggers the delete action implemented asfollows

30 3 Post Management

public function actionDelete()

if(Yiiapp()-gtrequest-gtisPostRequest)

we only allow deletion via POST request

$this-gtloadModel()-gtdelete()

if(isset($ POST[rsquoajaxrsquo]))

$this-gtredirect(array(rsquoindexrsquo))

else

throw new CHttpException(400rsquoInvalid request Please do not repeat this request againrsquo)

The above code is the one generated by the yiic tool without any change We wouldlike to explain a little bit more about the checking on $ POST[rsquoajaxrsquo] The [CGridView]widget has a very nice feature that its sorting pagination and deletion operations are alldone in AJAX mode by default That means the whole page does not get reloaded if anyof the above operations is performed However it is also possible that the widget runs innon-AJAX mode (by setting its ajaxUpdate property to be false or disabling JavaScript onthe client side) It is necessary for the delete action to differentiate these two scenarios ifthe delete request is made via AJAX we should not redirect the user browser otherwisewe should

Deleting a post should also cause the deletion of all comments for that post In additionwe should also update the tbl tag table regarding the tags for the deleted post Both ofthese tasks can be achieved by writing an afterDelete method in the Post model class asfollows

protected function afterDelete()

parentafterDelete()

Commentmodel()-gtdeleteAll(rsquopost id=rsquo$this-gtid)

Tagmodel()-gtupdateFrequency($this-gttags rsquorsquo)

The above code is very straightforward it first deletes all those comments whose post id

is the same as the ID of the deleted post it then updates the tbl tag table for the tags

of the deleted post

34 Managing Posts 31

Tip We have to explicitly delete all comments for the deleted post here becauseSQLite does not really support foreign key constraints In a DBMS that supportsthis constraint (such as MySQL PostgreSQL) the foreign key constraint can be setup such that the DBMS automatically deletes the related comments if the post isdeleted In that case we no longer this explicit deletion call in our code

32 3 Post Management

Chapter 4

Comment Management

41 Customizing Comment Model

For the Comment model we mainly need to customize the rules() and attributeLabels()

methods The attributeLabels() method returns a mapping between attribute namesand attribute labels We do not need to touch relations() since the code generated bythe yiic tool is good enough

411 Customizing rules() Method

We first customize the validation rules generated by the yiic tool The following rules areused for comments

public function rules()

return array(

array(rsquocontent author emailrsquo rsquorequiredrsquo)

array(rsquoauthor email urlrsquo rsquolengthrsquo rsquomaxrsquo=gt128)

array(rsquoemailrsquorsquoemailrsquo)

array(rsquourlrsquorsquourlrsquo)

)

In the above we specify that the author email and content attributes are required thelength of author email and url cannot exceed 128 the email attribute must be a validemail address and the url attribute must be a valid URL

412 Customizing attributeLabels() Method

We then customize the attributeLabels() method to declare the label display for eachmodel attribute This method returns an array consisting of name-label pairs When wecall CHtmlactiveLabel() to display an attribute label

34 4 Comment Management

public function attributeLabels()

return array(

rsquoidrsquo =gt rsquoIdrsquo

rsquocontentrsquo =gt rsquoCommentrsquo

rsquostatusrsquo =gt rsquoStatusrsquo

rsquocreate timersquo =gt rsquoCreate Timersquo

rsquoauthorrsquo =gt rsquoNamersquo

rsquoemailrsquo =gt rsquoEmailrsquo

rsquourlrsquo =gt rsquoWebsitersquo

rsquopost idrsquo =gt rsquoPostrsquo

)

Tip If the label for an attribute is not declared in attributeLabels() an algo-rithm will be used to generate an appropriate label For example a label VerifyCode will be generated for attributes verify code or verifyCode

413 Customizing Saving Process

Because we want to record the creation time of a comment we override the beforeSave()

method of Comment like we do for the Post model

protected function beforeSave()

if(parentbeforeSave())

if($this-gtisNewRecord)

$this-gtcreate time=time()

return true

else

return false

42 Creating and Displaying Comments

In this section we implement the comment display and creation features

421 Displaying Comments

Instead of displaying and creating comments on individual pages we use the post detailpage (generated by the view action of PostController) Below the post content display

42 Creating and Displaying Comments 35

we display first a list of comments belonging to that post and then a comment creationform

In order to display comments on the post detail page we modify the view script wwwrootblogprotectedviewspostviewphp as follows

ltdiv id=commentsgt

ltphp if($model-gtcommentCountgt=1) gt

lth3gt

ltphp echo $model-gtcommentCount rsquocomment(s)rsquo gt

lth3gt

ltphp $this-gtrenderPartial(rsquo commentsrsquoarray(

rsquopostrsquo=gt$model

rsquocommentsrsquo=gt$model-gtcomments

)) gt

ltphp endif gt

ltdivgt

In the above we call renderPartial() to render a partial view named comments to displaythe list of comments belonging to the current post Note that in the view we use theexpression $model-gtcomments to retrieve the comments for the post This is valid becausewe have declared a comments relation in the Post class Evaluating this expression wouldtrigger an implicit JOIN database query to bring back the proper comments This featureis known as lazy relational query

The partial view comments is not very interesting It mainly goes through every commentand displays the detail of it Interested readers may refer to wwwrootyiidemosblog

protectedpost commentsphp

422 Creating Comments

To handle comment creation we first modify the actionView() method of PostController

as follows

public function actionView()

$post=$this-gtloadModel()

$comment=$this-gtnewComment($post)

$this-gtrender(rsquoviewrsquoarray(

rsquomodelrsquo=gt$post

rsquocommentrsquo=gt$comment

))

36 4 Comment Management

protected function newComment($post)

$comment=new Comment

if(isset($ POST[rsquoCommentrsquo]))

$comment-gtattributes=$ POST[rsquoCommentrsquo]

if($post-gtaddComment($comment))

if($comment-gtstatus==CommentSTATUS PENDING)

Yiiapp()-gtuser-gtsetFlash(rsquocommentSubmittedrsquorsquoThank yoursquo)

$this-gtrefresh()

return $comment

In the above we call the newComment() method before we render view In the newComment()

method we generate a Comment instance and check if the comment form is submitted If sowe try to add the comment for the post by calling $post-gtaddComment($comment) If it goesthrough we refresh the post detail page In case the comment needs to be approved we willshow a flash message to indicate this decision A flash message is usually a confirmationmessage displayed to end users If the user clicks on the refresh button of his browser themessage will disappear

We also need to modify wwwrootblogprotectedviewspostviewphp furthermore

ltdiv id=commentsgt

lth3gtLeave a Commentlth3gt

ltphp if(Yiiapp()-gtuser-gthasFlash(rsquocommentSubmittedrsquo)) gt

ltdiv class=successgt

ltphp echo Yiiapp()-gtuser-gtgetFlash(rsquocommentSubmittedrsquo) gt

ltdivgt

ltphp else gt

ltphp $this-gtrenderPartial(rsquocomment formrsquoarray(

rsquomodelrsquo=gt$comment

)) gt

ltphp endif gt

ltdivgtlt-- comments --gt

43 Managing Comments 37

In the above code we display the flash message if it is available If not we displaythe comment input form by rendering the partial view wwwrootblogprotectedviews

comment formphp

43 Managing Comments

Comment management includes updating deleting and approving comments These op-erations are implemented as actions in the CommentController class

431 Updating and Deleting Comments

The code generated by yiic for updating and deleting comments remains largely un-changed

432 Approving Comments

When comments are newly created they are in pending approval status and need to beapproved in order to be visible to guest users Approving a comment is mainly aboutchanging the status column of the comment

We create an actionApprove() method in CommentController as follows

public function actionApprove()

if(Yiiapp()-gtrequest-gtisPostRequest)

$comment=$this-gtloadModel()

$comment-gtapprove()

$this-gtredirect(array(rsquoindexrsquo))

else

throw new CHttpException(400rsquoInvalid requestrsquo)

In the above when the approve action is invoked via a POST request we call the approve()

method defined in the Comment model to change the status We then redirect the userbrowser to the page displaying the post that this comment belongs to

We also modify the actionIndex() method of Comment to show all comments We wouldlike to see comments pending approved to show up first

public function actionIndex()

38 4 Comment Management

$dataProvider=new CActiveDataProvider(rsquoCommentrsquo array(

rsquocriteriarsquo=gtarray(

rsquowithrsquo=gtrsquopostrsquo

rsquoorderrsquo=gtrsquotstatus tcreate time DESCrsquo

)

))

$this-gtrender(rsquoindexrsquoarray(

rsquodataProviderrsquo=gt$dataProvider

))

Notice that in the above code because both tbl post and tbl comment have columns statusand create time we need to disambiguate the corresponding column reference by prefixingthem with table alias names As described in the guide the alias for the primary table ina relational query is always t Therefore we are prefixing t to the status and create time

columns in the above code

Like the post index view the index view for CommentController uses [CListView] to displaythe comment list which in turn uses the partial view wwwrootblogprotectedviews

comment viewphp to display the detail of each individual comment We will not go intodetails here Interested readers may refer to the corresponding file in the blog demowwwrootyiidemosblogprotectedviewscomment viewphp

Chapter 5

Portlets

51 Customizing Page Layout

Features like rdquothe most recent commentsrdquo rdquotag cloudrdquo are better to be implemented inportlets A portlet is a pluggable user interface component that renders a fragment ofHTML code In this section we describe how to set up the portlet architecture for ourblog application

Before we go into details about developing portlets we first need to adjust our page layoutso that the portlets can be placed in a side bar

A page layout determines how to share common sections (eg header footer) amongdifferent pages and how to embed contents that are specific for different pages In Yiia page layout is represented as a view under the protectedviewslayouts folder Whenwe call the render() method in a controller the rendering result will be automaticallyembedded into this layout view before it is displayed to end users The default layoutview is named as main which means the wwwrootblogprotectedviewslayoutsmain

php file in our blog application

We could directly modify the main layout to add a side bar However doing so wouldmake some irrelevant pages such as about contact pages to have a side bar which is notalways desirable For this reason we need different layouts for different pages

We thus create two different layouts column1 and column2 The former displays the pagein a single column while the latter displays two columns in the page one of which is theside bar We will use column2 layout for the post and comment pages and column1 forthe rest of the pages Because both column1 and column2 still share the same header andfooter sections we let them to share the main layout

Based on the above description we create the column1 layout as follows

ltphp $this-gtbeginContent(rsquolayoutsmainrsquo) gt

ltdiv class=containergt

40 5 Portlets

ltdiv id=contentgt

ltphp echo $content gt

ltdivgtlt-- content --gt

ltdivgt

ltphp $this-gtendContent() gt

and the column2 layout as follows

ltphp $this-gtbeginContent(rsquolayoutsmainrsquo) gt

ltdiv class=containergt

ltdiv class=span-18gt

ltdiv id=contentgt

ltphp echo $content gt

ltdivgtlt-- content --gt

ltdivgt

ltdiv class=span-6 lastgt

ltdiv id=sidebargt

portlets to be placed here

ltdivgtlt-- sidebar --gt

ltdivgt

ltdivgt

ltphp $this-gtendContent() gt

As we can see both column1 and column2 are enclosed in a pair of beginContent andendContent calls They essentially use the specified view layoutsmain to decorate theenclosed content In all these layout views the variable $content refers to the dynamiccontent to be embedded into the layout

Besides customizing the layout view file we also need to adjust the CSS file wwwroot

blogcssmaincss so that the overall appearance would look like what we see in the blogdemo We will not go into details here

Info The skeleton application generated by the yiic tool uses the BluePrint CSSframework It provides a nice font reset and a robust grid system For more detailson how to use this framework please refer to its documentation

After creating the new layouts we need to set the layout property of controllers to makeuse of them So far we have three controllers SiteController PostController andCommentController We set SiteController to use column1 as the default layout whilethe rest two of the controllers use column2 for example

class PostController extends Controller

52 Creating User Menu Portlet 41

public $layout=rsquocolumn2rsquo

Tip If some view of the controller does not use the same layout as declaredby the layout property it may customize it in the view by a simple assignment$this-gtlayout=rsquonewlayoutrsquo

52 Creating User Menu Portlet

Based on the requirements analysis we need three different portlets the rdquouser menurdquoportlet the rdquotag cloudrdquo portlet and the rdquorecent commentsrdquo portlet We will develop theseportlets by extending the [CPortlet] widget provided by Yii

In this section we will develop our first concrete portlet - the user menu portlet whichdisplays a list of menu items that are only available to authenticated users The menucontains four items

bull Approve Comments a hyperlink that leads to a list of comments pending approval

bull Create New Post a hyperlink that leads to the post creation page

bull Manage Posts a hyperlink that leads to the post management page

bull Logout a link button that would log out the current user

521 Creating UserMenu Class

We create the UserMenu class to represent the logic part of the user menu portlet Theclass is saved in the file wwwrootblogprotectedcomponentsUserMenuphp which has thefollowing content

Yiiimport(rsquoziiwidgetsCPortletrsquo)

class UserMenu extends CPortlet

public function init()

$this-gttitle=CHtmlencode(Yiiapp()-gtuser-gtname)

parentinit()

42 5 Portlets

protected function renderContent()

$this-gtrender(rsquouserMenursquo)

The UserMenu class extends from the CPortlet class that we created previously It overridesboth the init() method and the renderContent() method of CPortlet The former setsthe portlet title to be the name of the current user the latter generates the portlet bodycontent by rendering a view named userMenu

Tip Notice that we have to explicitly include the CPortlet class by calling Yii

import() before we refer to it the first time This is because CPortlet is part of thezii project ndash the official extension library for Yii For performance considerationclasses in this project are not listed as core classes Therefore we have to import itbefore we use it the first time

522 Creating userMenu View

Next we create the userMenu view which is saved in the file wwwrootblogprotected

componentsviewsuserMenuphp

ltulgt

ltligtltphp echo CHtmllink(rsquoCreate New Postrsquoarray(rsquopostcreatersquo)) gtltligt

ltligtltphp echo CHtmllink(rsquoManage Postsrsquoarray(rsquopostadminrsquo)) gtltligt

ltligtltphp echo CHtmllink(rsquoApprove Commentsrsquoarray(rsquocommentindexrsquo))

rsquo (rsquo Commentmodel()-gtpendingCommentCount rsquo)rsquo gtltligt

ltligtltphp echo CHtmllink(rsquoLogoutrsquoarray(rsquositelogoutrsquo)) gtltligt

ltulgt

Info By default view files for a widget should be placed under the views sub-directory of the directory containing the widget class file The file name must bethe same as the view name

523 Using UserMenu Portlet

It is time for us to make use of our newly completed UserMenu portlet We modify thelayout view file wwwrootblogprotectedviewslayoutscolumn2php as follows

53 Creating Tag Cloud Portlet 43

ltdiv id=sidebargt

ltphp if(Yiiapp()-gtuser-gtisGuest) $this-gtwidget(rsquoUserMenursquo) gt

ltdivgt

In the above we call the widget() method to generate and execute an instance of theUserMenu class Because the portlet should only be displayed to authenticated users weonly call widget() when the isGuest property of the current user is false (meaning theuser is authenticated)

524 Testing UserMenu Portlet

Letrsquos test what we have so far

1 Open a browser window and enter the URL httpwwwexamplecomblogindex

php Verify that there is nothing displayed in the side bar section of the page

2 Click on the Login hyperlink and fill out the login form to login If successful verifythat the UserMenu portlet appears in the side bar and the portlet has the usernameas its title

3 Click on the rsquoLogoutrsquo hyperlink in the UserMenu portlet Verify that the logout actionis successful and the UserMenu portlet disappears

525 Summary

What we have created is a portlet that is highly reusable We can easily reuse it in adifferent project with little or no modification Moreover the design of this portlet followsclosely the philosophy that logic and presentation should be separated While we did notpoint this out in the previous sections such practice is used nearly everywhere in a typicalYii application

53 Creating Tag Cloud Portlet

Tag cloud displays a list of post tags with visual decorations hinting the popularity of eachindividual tag

531 Creating TagCloud Class

We create the TagCloud class in the file wwwrootblogprotectedcomponentsTagCloud

php The file has the following content

44 5 Portlets

Yiiimport(rsquoziiwidgetsCPortletrsquo)

class TagCloud extends CPortlet

public $title=rsquoTagsrsquo

public $maxTags=20

protected function renderContent()

$tags=Tagmodel()-gtfindTagWeights($this-gtmaxTags)

foreach($tags as $tag=gt$weight)

$link=CHtmllink(CHtmlencode($tag) array(rsquopostindexrsquorsquotagrsquo=gt$tag))

echo CHtmltag(rsquospanrsquo array(

rsquoclassrsquo=gtrsquotagrsquo

rsquostylersquo=gtfont-size$weightpt) $link)n

Unlike the UserMenu portlet the TagCloud portlet does not use a view Instead its presen-tation is done in the renderContent() method This is because the presentation does notcontain much HTML tags

We display each tag as a hyperlink to the post index page with the corresponding tagparameter The font size of each tag link is adjusted according to their relative weightamong other tags If a tag has higher frequency value than the other it will have a biggerfont size

532 Using TagCloud Portlet

Usage of the TagCloud portlet is very simple We modify the layout file wwwrootblog

protectedviewslayoutsmainphp as follows

ltdiv id=sidebargt

ltphp if(Yiiapp()-gtuser-gtisGuest) $this-gtwidget(rsquoUserMenursquo) gt

ltphp $this-gtwidget(rsquoTagCloudrsquo array(

rsquomaxTagsrsquo=gtYiiapp()-gtparams[rsquotagCloudCountrsquo]

)) gt

ltdivgt

54 Creating Recent Comments Portlet 45

54 Creating Recent Comments Portlet

In this section we create the last portlet that displays a list of comments recently pub-lished

541 Creating RecentComments Class

We create the RecentComments class in the file wwwrootblogprotectedcomponentsRecentCommentsphp The file has the following content

Yiiimport(rsquoziiwidgetsCPortletrsquo)

class RecentComments extends CPortlet

public $title=rsquoRecent Commentsrsquo

public $maxComments=10

public function getRecentComments()

return Commentmodel()-gtfindRecentComments($this-gtmaxComments)

protected function renderContent()

$this-gtrender(rsquorecentCommentsrsquo)

In the above we invoke the findRecentComments method which is defined in the Comment

class as follows

class Comment extends CActiveRecord

public function findRecentComments($limit=10)

return $this-gtwith(rsquopostrsquo)-gtfindAll(array(

rsquoconditionrsquo=gtrsquotstatus=rsquoselfSTATUS APPROVED

rsquoorderrsquo=gtrsquotcreate time DESCrsquo

rsquolimitrsquo=gt$limit

))

46 5 Portlets

542 Creating recentComments View

The recentComments view is saved in the file wwwrootblogprotectedcomponentsviews

recentCommentsphp It simply displays every comment returned by the RecentComments

getRecentComments() method

543 Using RecentComments Portlet

We modify the layout file wwwrootblogprotectedviewslayoutsmainphp to embedthis last portlet

ltdiv id=sidebargt

ltphp if(Yiiapp()-gtuser-gtisGuest) $this-gtwidget(rsquoUserMenursquo) gt

ltphp $this-gtwidget(rsquoTagCloudrsquo array(

rsquomaxTagsrsquo=gtYiiapp()-gtparams[rsquotagCloudCountrsquo]

)) gt

ltphp $this-gtwidget(rsquoRecentCommentsrsquo array(

rsquomaxCommentsrsquo=gtYiiapp()-gtparams[rsquorecentCommentCountrsquo]

)) gt

ltdivgt

Chapter 6

Final Work

61 Beautifying URLs

The URLs linking various pages of our blog application currently look ugly For examplethe URL for the page showing a post looks like the following

indexphpr=postshowampid=1

In this section we describe how to beautify these URLs and make them SEO-friendlyOur goal is to be able to use the following URLs in the application

1 indexphppostsyii leads to the page showing a list of posts with tag yii

2 indexphppost2A+Test+Post leads to the page showing the detail of the postwith ID 2 whose title is A Test Post

3 indexphppostupdate1 leads to the page that allows updating the post with ID1

Note that in the second URL format we include the post title in the URL This is mainlyto make the URL SEO friendly It is said that search engines may also respect the wordsfound in a URL when it is being indexed

To achieve our goal we modify the application configuration as follows

return array(

rsquocomponentsrsquo=gtarray(

rsquourlManagerrsquo=gtarray(

rsquourlFormatrsquo=gtrsquopathrsquo

rsquorulesrsquo=gtarray(

48 6 Final Work

rsquopostltidd+gtlttitlegtrsquo=gtrsquopostviewrsquorsquopostslttaggtrsquo=gtrsquopostindexrsquo

)

)

)

)

In the above we configure the urlManager component by setting its urlFormat propertyto be path and adding a set of rules

The rules are used by urlManager to parse and create the URLs in the desired formatFor example the first rule says that if a URL indexphppostsyii is requested theurlManager component should be responsible to dispatch the request to the route post

index and generate a tag GET parameter with the value yii On the other hand whencreating a URL with the route postindex and parameter tag the urlManager componentwill also use this rule to generate the desired URL indexphppostsyii For this reasonwe say that urlManager is a two-way URL manager

The urlManager component can further beautify our URLs such as hiding indexphp inthe URLs appending suffix like html to the URLs We can obtain these features easilyby configuring various properties of urlManager in the application configuration For moredetails please refer to the Guide

62 Logging Errors

A production Web application often needs sophisticated logging for various events In ourblog application we would like to log the errors occurring when it is being used Sucherrors could be programming mistakes or usersrsquo misuse of the system Logging these errorswill help us to improve the blog application

We enable the error logging by modifying the application configuration as follows

return array(

rsquopreloadrsquo=gtarray(rsquologrsquo)

rsquocomponentsrsquo=gtarray(

rsquologrsquo=gtarray(

rsquoclassrsquo=gtrsquoCLogRouterrsquo

rsquoroutesrsquo=gtarray(

array(

rsquoclassrsquo=gtrsquoCFileLogRoutersquo

rsquolevelsrsquo=gtrsquoerror warningrsquo

63 Final Tune-up and Deployment 49

)

)

)

)

)

With the above configuration if an error or warning occurs detailed information will belogged and saved in a file located under the directory wwwrootblogprotectedruntime

The log component offers more advanced features such as sending log messages to a listof email addresses displaying log messages in JavaScript console window etc For moredetails please refer to the Guide

63 Final Tune-up and Deployment

We are close to finish our blog application Before deployment we would like to do sometune-ups

631 Changing Home Page

We change to use the post list page as the home page We modify the application config-uration as follows

return array(

rsquodefaultControllerrsquo=gtrsquopostrsquo

)

Tip Because PostController already declares list to be its default action whenwe access the home page of the application we will see the result generated by thelist action of the post controller

632 Enabling Schema Caching

Because ActiveRecord relies on the metadata about tables to determine the column infor-mation it takes time to read the metadata and analyze it This may not be a problemduring development stage but for an application running in production mode it is a totalwaste of time if the database schema does not change Therefore we should enable theschema caching by modifying the application configuration as follows

50 6 Final Work

return array(

rsquocomponentsrsquo=gtarray(

rsquocachersquo=gtarray(

rsquoclassrsquo=gtrsquoCDbCachersquo

)

rsquodbrsquo=gtarray(

rsquoclassrsquo=gtrsquosystemdbCDbConnectionrsquo

rsquoconnectionStringrsquo=gtrsquosqlitewwwrootblogprotecteddatablogdbrsquo

rsquoschemaCachingDurationrsquo=gt3600

)

)

)

In the above we first add a cache component which uses a default SQLite database as thecaching storage If our server is equipped with other caching extensions such as APCwe could change to use them as well We also modify the db component by setting itsschemaCachingDuration property to be 3600 which means the parsed database schemadata can remain valid in cache for 3600 seconds

633 Disabling Debugging Mode

We modify the entry script file wwwrootblogindexphp by removing the line definingthe constant YII DEBUG This constant is useful during development stage because it allowsYii to display more debugging information when an error occurs However when theapplication is running in production mode displaying debugging information is not agood idea because it may contain sensitive information such as where the script file islocated and the content in the file etc

634 Deploying the Application

The final deployment process manly involves copying the directory wwwrootblog to thetarget directory The following checklist shows every needed step

1 Install Yii in the target place if it is not available

2 Copy the entire directory wwwrootblog to the target place

3 Edit the entry script file indexphp by pointing the $yii variable to the new Yiibootstrap file

4 Edit the file protectedyiicphp by setting the $yiic variable to be the new Yiiyiicphp file

64 Future Enhancements 51

5 Change the permission of the directories assets and protectedruntime so that theyare writable by the Web server process

64 Future Enhancements

641 Using a Theme

Without writing any code our blog application is already themeable To use a themewe mainly need to develop the theme by writing customized view files in the theme Forexample to use a theme named classic that uses a different page layout we would createa layout view file wwwrootblogthemesclassicviewslayoutsmainphp We also needto change the application configuration to indicate our choice of the classic theme

return array(

rsquothemersquo=gtrsquoclassicrsquo

)

642 Internationalization

We may also internationalize our blog application so that its pages can be displayed indifferent languages This mainly involves efforts in two aspects

First we may create view files in different languages For example for the list pageof PostController we can create a view file wwwrootblogprotectedviewspostzh cn

listphp When the application is configured to use simplified Chinese (the language codeis zh cn) Yii will automatically use this new view file instead of the original one

Second we may create message translations for those messages generated by code Themessage translations should be saved as files under the directory wwwrootblogprotected

messages We also need to modify the code where we use text strings by enclosing themin the method call Yiit()

For more details about internationalization please refer to the Guide

643 Improving Performance with Cache

While the Yii framework itself is very efficient it is not necessarily true that an applicationwritten in Yii efficient There are several places in our blog application that we can improvethe performance For example the tag clould portlet could be one of the performancebottlenecks because it involves complex database query and PHP logic

52 6 Final Work

We can make use of the sophisticated caching feature provided by Yii to improve theperformance One of the most useful components in Yii is COutputCache which caches afragment of page display so that the underlying code generating the fragment does not needto be executed for every request For example in the layout file wwwrootblogprotected

viewslayoutsmainphp we can enclose the tag cloud portlet with COutputCache

ltphp if($this-gtbeginCache(rsquotagCloudrsquo array(rsquodurationrsquo=gt3600))) gt

ltphp $this-gtwidget(rsquoTagCloudrsquo array(

rsquomaxTagsrsquo=gtYiiapp()-gtparams[rsquotagCloudCountrsquo]

)) gt

ltphp $this-gtendCache() gt

With the above code the tag cloud display will be served from cache instead of beinggenerated on-the-fly for every request The cached content will remain valid in cache for3600 seconds

644 Adding New Features

Our blog application only has very basic functionalities To become a complete blogsystem more features are needed for example calendar portlet email notifications postcategorization archived post portlet and so on We will leave the implementation of thesefeatures to interested readers

  • Contents
  • License
  • Getting Started
    • Building a Blog System using Yii
    • Testdriving with Yii
      • Installing Yii
      • Creating Skeleton Application
      • Application Workflow
        • Requirements Analysis
        • Overall Design
          • Initial Prototyping
            • Setting Up Database
              • Creating Database
              • Establishing Database Connection
                • Scaffolding
                • Authenticating User
                • Summary
                  • Post Management
                    • Customizing Post Model
                      • Customizing rules() Method
                      • Customizing relations() Method
                      • Representing Status in Text
                        • Creating and Updating Posts
                          • Customizing Access Control
                          • Customizing create and update Operations
                            • Displaying Posts
                              • Customizing view Operation
                              • Customizing index Operation
                                • Managing Posts
                                  • Listing Posts in Tabular View
                                  • Deleting Posts
                                      • Comment Management
                                        • Customizing Comment Model
                                          • Customizing rules() Method
                                          • Customizing attributeLabels() Method
                                          • Customizing Saving Process
                                            • Creating and Displaying Comments
                                              • Displaying Comments
                                              • Creating Comments
                                                • Managing Comments
                                                  • Updating and Deleting Comments
                                                  • Approving Comments
                                                      • Portlets
                                                        • Customizing Page Layout
                                                        • Creating User Menu Portlet
                                                          • Creating UserMenu Class
                                                          • Creating userMenu View
                                                          • Using UserMenu Portlet
                                                          • Testing UserMenu Portlet
                                                          • Summary
                                                            • Creating Tag Cloud Portlet
                                                              • Creating TagCloud Class
                                                              • Using TagCloud Portlet
                                                                • Creating Recent Comments Portlet
                                                                  • Creating RecentComments Class
                                                                  • Creating recentComments View
                                                                  • Using RecentComments Portlet
                                                                      • Final Work
                                                                        • Beautifying URLs
                                                                        • Logging Errors
                                                                        • Final Tune-up and Deployment
                                                                          • Changing Home Page
                                                                          • Enabling Schema Caching
                                                                          • Disabling Debugging Mode
                                                                          • Deploying the Application
                                                                            • Future Enhancements
                                                                              • Using a Theme
                                                                              • Internationalization
                                                                              • Improving Performance with Cache
                                                                              • Adding New Features
Page 36: Yii Blog 1.1.0 Demo Tutor

34 Managing Posts 29

The above code is very similar to that in actionIndex() The main difference is in thequery criteria Here we do not specify any particular query criteria since we want to seeall posts We do customize the sort property of the data provider so that by default theposts are shown in the order of their statuses and update time

Below is the code for the admin view

lth1gtManage Postslth1gt

ltphp $this-gtwidget(rsquoziiwidgetsgridCGridViewrsquo array(

rsquodataProviderrsquo=gt$dataProvider

rsquocolumnsrsquo=gtarray(

array(

rsquonamersquo=gtrsquotitlersquo

rsquotypersquo=gtrsquorawrsquo

rsquovaluersquo=gtrsquoCHtmllink(CHtmlencode($data-gttitle) $data-gturl)rsquo

)

array(

rsquonamersquo=gtrsquostatusrsquo

rsquovaluersquo=gtrsquoLookupitem(PostStatus$data-gtstatus)rsquo

)

rsquocreate timedatetimersquo

rsquoupdate timedatetimersquo

array(

rsquoclassrsquo=gtrsquoCButtonColumnrsquo

)

)

)) gt

We use [CGridView] to display the posts It allows us to sort by a column and paginatethrough the posts if there are too many to be displayed in a single page Our change ismainly about how to display each column For example for the title column we specifythat it should be displayed as a hyperlink that points to the detailed view of the post

Tip When displaying text we call CHtmlencode() to encode HTML entities init This prevents from cross-site scripting attack

342 Deleting Posts

In the admin data grid there is a delete button in each row Clicking on the button shoulddelete the corresponding post Internally this triggers the delete action implemented asfollows

30 3 Post Management

public function actionDelete()

if(Yiiapp()-gtrequest-gtisPostRequest)

we only allow deletion via POST request

$this-gtloadModel()-gtdelete()

if(isset($ POST[rsquoajaxrsquo]))

$this-gtredirect(array(rsquoindexrsquo))

else

throw new CHttpException(400rsquoInvalid request Please do not repeat this request againrsquo)

The above code is the one generated by the yiic tool without any change We wouldlike to explain a little bit more about the checking on $ POST[rsquoajaxrsquo] The [CGridView]widget has a very nice feature that its sorting pagination and deletion operations are alldone in AJAX mode by default That means the whole page does not get reloaded if anyof the above operations is performed However it is also possible that the widget runs innon-AJAX mode (by setting its ajaxUpdate property to be false or disabling JavaScript onthe client side) It is necessary for the delete action to differentiate these two scenarios ifthe delete request is made via AJAX we should not redirect the user browser otherwisewe should

Deleting a post should also cause the deletion of all comments for that post In additionwe should also update the tbl tag table regarding the tags for the deleted post Both ofthese tasks can be achieved by writing an afterDelete method in the Post model class asfollows

protected function afterDelete()

parentafterDelete()

Commentmodel()-gtdeleteAll(rsquopost id=rsquo$this-gtid)

Tagmodel()-gtupdateFrequency($this-gttags rsquorsquo)

The above code is very straightforward it first deletes all those comments whose post id

is the same as the ID of the deleted post it then updates the tbl tag table for the tags

of the deleted post

34 Managing Posts 31

Tip We have to explicitly delete all comments for the deleted post here becauseSQLite does not really support foreign key constraints In a DBMS that supportsthis constraint (such as MySQL PostgreSQL) the foreign key constraint can be setup such that the DBMS automatically deletes the related comments if the post isdeleted In that case we no longer this explicit deletion call in our code

32 3 Post Management

Chapter 4

Comment Management

41 Customizing Comment Model

For the Comment model we mainly need to customize the rules() and attributeLabels()

methods The attributeLabels() method returns a mapping between attribute namesand attribute labels We do not need to touch relations() since the code generated bythe yiic tool is good enough

411 Customizing rules() Method

We first customize the validation rules generated by the yiic tool The following rules areused for comments

public function rules()

return array(

array(rsquocontent author emailrsquo rsquorequiredrsquo)

array(rsquoauthor email urlrsquo rsquolengthrsquo rsquomaxrsquo=gt128)

array(rsquoemailrsquorsquoemailrsquo)

array(rsquourlrsquorsquourlrsquo)

)

In the above we specify that the author email and content attributes are required thelength of author email and url cannot exceed 128 the email attribute must be a validemail address and the url attribute must be a valid URL

412 Customizing attributeLabels() Method

We then customize the attributeLabels() method to declare the label display for eachmodel attribute This method returns an array consisting of name-label pairs When wecall CHtmlactiveLabel() to display an attribute label

34 4 Comment Management

public function attributeLabels()

return array(

rsquoidrsquo =gt rsquoIdrsquo

rsquocontentrsquo =gt rsquoCommentrsquo

rsquostatusrsquo =gt rsquoStatusrsquo

rsquocreate timersquo =gt rsquoCreate Timersquo

rsquoauthorrsquo =gt rsquoNamersquo

rsquoemailrsquo =gt rsquoEmailrsquo

rsquourlrsquo =gt rsquoWebsitersquo

rsquopost idrsquo =gt rsquoPostrsquo

)

Tip If the label for an attribute is not declared in attributeLabels() an algo-rithm will be used to generate an appropriate label For example a label VerifyCode will be generated for attributes verify code or verifyCode

413 Customizing Saving Process

Because we want to record the creation time of a comment we override the beforeSave()

method of Comment like we do for the Post model

protected function beforeSave()

if(parentbeforeSave())

if($this-gtisNewRecord)

$this-gtcreate time=time()

return true

else

return false

42 Creating and Displaying Comments

In this section we implement the comment display and creation features

421 Displaying Comments

Instead of displaying and creating comments on individual pages we use the post detailpage (generated by the view action of PostController) Below the post content display

42 Creating and Displaying Comments 35

we display first a list of comments belonging to that post and then a comment creationform

In order to display comments on the post detail page we modify the view script wwwrootblogprotectedviewspostviewphp as follows

ltdiv id=commentsgt

ltphp if($model-gtcommentCountgt=1) gt

lth3gt

ltphp echo $model-gtcommentCount rsquocomment(s)rsquo gt

lth3gt

ltphp $this-gtrenderPartial(rsquo commentsrsquoarray(

rsquopostrsquo=gt$model

rsquocommentsrsquo=gt$model-gtcomments

)) gt

ltphp endif gt

ltdivgt

In the above we call renderPartial() to render a partial view named comments to displaythe list of comments belonging to the current post Note that in the view we use theexpression $model-gtcomments to retrieve the comments for the post This is valid becausewe have declared a comments relation in the Post class Evaluating this expression wouldtrigger an implicit JOIN database query to bring back the proper comments This featureis known as lazy relational query

The partial view comments is not very interesting It mainly goes through every commentand displays the detail of it Interested readers may refer to wwwrootyiidemosblog

protectedpost commentsphp

422 Creating Comments

To handle comment creation we first modify the actionView() method of PostController

as follows

public function actionView()

$post=$this-gtloadModel()

$comment=$this-gtnewComment($post)

$this-gtrender(rsquoviewrsquoarray(

rsquomodelrsquo=gt$post

rsquocommentrsquo=gt$comment

))

36 4 Comment Management

protected function newComment($post)

$comment=new Comment

if(isset($ POST[rsquoCommentrsquo]))

$comment-gtattributes=$ POST[rsquoCommentrsquo]

if($post-gtaddComment($comment))

if($comment-gtstatus==CommentSTATUS PENDING)

Yiiapp()-gtuser-gtsetFlash(rsquocommentSubmittedrsquorsquoThank yoursquo)

$this-gtrefresh()

return $comment

In the above we call the newComment() method before we render view In the newComment()

method we generate a Comment instance and check if the comment form is submitted If sowe try to add the comment for the post by calling $post-gtaddComment($comment) If it goesthrough we refresh the post detail page In case the comment needs to be approved we willshow a flash message to indicate this decision A flash message is usually a confirmationmessage displayed to end users If the user clicks on the refresh button of his browser themessage will disappear

We also need to modify wwwrootblogprotectedviewspostviewphp furthermore

ltdiv id=commentsgt

lth3gtLeave a Commentlth3gt

ltphp if(Yiiapp()-gtuser-gthasFlash(rsquocommentSubmittedrsquo)) gt

ltdiv class=successgt

ltphp echo Yiiapp()-gtuser-gtgetFlash(rsquocommentSubmittedrsquo) gt

ltdivgt

ltphp else gt

ltphp $this-gtrenderPartial(rsquocomment formrsquoarray(

rsquomodelrsquo=gt$comment

)) gt

ltphp endif gt

ltdivgtlt-- comments --gt

43 Managing Comments 37

In the above code we display the flash message if it is available If not we displaythe comment input form by rendering the partial view wwwrootblogprotectedviews

comment formphp

43 Managing Comments

Comment management includes updating deleting and approving comments These op-erations are implemented as actions in the CommentController class

431 Updating and Deleting Comments

The code generated by yiic for updating and deleting comments remains largely un-changed

432 Approving Comments

When comments are newly created they are in pending approval status and need to beapproved in order to be visible to guest users Approving a comment is mainly aboutchanging the status column of the comment

We create an actionApprove() method in CommentController as follows

public function actionApprove()

if(Yiiapp()-gtrequest-gtisPostRequest)

$comment=$this-gtloadModel()

$comment-gtapprove()

$this-gtredirect(array(rsquoindexrsquo))

else

throw new CHttpException(400rsquoInvalid requestrsquo)

In the above when the approve action is invoked via a POST request we call the approve()

method defined in the Comment model to change the status We then redirect the userbrowser to the page displaying the post that this comment belongs to

We also modify the actionIndex() method of Comment to show all comments We wouldlike to see comments pending approved to show up first

public function actionIndex()

38 4 Comment Management

$dataProvider=new CActiveDataProvider(rsquoCommentrsquo array(

rsquocriteriarsquo=gtarray(

rsquowithrsquo=gtrsquopostrsquo

rsquoorderrsquo=gtrsquotstatus tcreate time DESCrsquo

)

))

$this-gtrender(rsquoindexrsquoarray(

rsquodataProviderrsquo=gt$dataProvider

))

Notice that in the above code because both tbl post and tbl comment have columns statusand create time we need to disambiguate the corresponding column reference by prefixingthem with table alias names As described in the guide the alias for the primary table ina relational query is always t Therefore we are prefixing t to the status and create time

columns in the above code

Like the post index view the index view for CommentController uses [CListView] to displaythe comment list which in turn uses the partial view wwwrootblogprotectedviews

comment viewphp to display the detail of each individual comment We will not go intodetails here Interested readers may refer to the corresponding file in the blog demowwwrootyiidemosblogprotectedviewscomment viewphp

Chapter 5

Portlets

51 Customizing Page Layout

Features like rdquothe most recent commentsrdquo rdquotag cloudrdquo are better to be implemented inportlets A portlet is a pluggable user interface component that renders a fragment ofHTML code In this section we describe how to set up the portlet architecture for ourblog application

Before we go into details about developing portlets we first need to adjust our page layoutso that the portlets can be placed in a side bar

A page layout determines how to share common sections (eg header footer) amongdifferent pages and how to embed contents that are specific for different pages In Yiia page layout is represented as a view under the protectedviewslayouts folder Whenwe call the render() method in a controller the rendering result will be automaticallyembedded into this layout view before it is displayed to end users The default layoutview is named as main which means the wwwrootblogprotectedviewslayoutsmain

php file in our blog application

We could directly modify the main layout to add a side bar However doing so wouldmake some irrelevant pages such as about contact pages to have a side bar which is notalways desirable For this reason we need different layouts for different pages

We thus create two different layouts column1 and column2 The former displays the pagein a single column while the latter displays two columns in the page one of which is theside bar We will use column2 layout for the post and comment pages and column1 forthe rest of the pages Because both column1 and column2 still share the same header andfooter sections we let them to share the main layout

Based on the above description we create the column1 layout as follows

ltphp $this-gtbeginContent(rsquolayoutsmainrsquo) gt

ltdiv class=containergt

40 5 Portlets

ltdiv id=contentgt

ltphp echo $content gt

ltdivgtlt-- content --gt

ltdivgt

ltphp $this-gtendContent() gt

and the column2 layout as follows

ltphp $this-gtbeginContent(rsquolayoutsmainrsquo) gt

ltdiv class=containergt

ltdiv class=span-18gt

ltdiv id=contentgt

ltphp echo $content gt

ltdivgtlt-- content --gt

ltdivgt

ltdiv class=span-6 lastgt

ltdiv id=sidebargt

portlets to be placed here

ltdivgtlt-- sidebar --gt

ltdivgt

ltdivgt

ltphp $this-gtendContent() gt

As we can see both column1 and column2 are enclosed in a pair of beginContent andendContent calls They essentially use the specified view layoutsmain to decorate theenclosed content In all these layout views the variable $content refers to the dynamiccontent to be embedded into the layout

Besides customizing the layout view file we also need to adjust the CSS file wwwroot

blogcssmaincss so that the overall appearance would look like what we see in the blogdemo We will not go into details here

Info The skeleton application generated by the yiic tool uses the BluePrint CSSframework It provides a nice font reset and a robust grid system For more detailson how to use this framework please refer to its documentation

After creating the new layouts we need to set the layout property of controllers to makeuse of them So far we have three controllers SiteController PostController andCommentController We set SiteController to use column1 as the default layout whilethe rest two of the controllers use column2 for example

class PostController extends Controller

52 Creating User Menu Portlet 41

public $layout=rsquocolumn2rsquo

Tip If some view of the controller does not use the same layout as declaredby the layout property it may customize it in the view by a simple assignment$this-gtlayout=rsquonewlayoutrsquo

52 Creating User Menu Portlet

Based on the requirements analysis we need three different portlets the rdquouser menurdquoportlet the rdquotag cloudrdquo portlet and the rdquorecent commentsrdquo portlet We will develop theseportlets by extending the [CPortlet] widget provided by Yii

In this section we will develop our first concrete portlet - the user menu portlet whichdisplays a list of menu items that are only available to authenticated users The menucontains four items

bull Approve Comments a hyperlink that leads to a list of comments pending approval

bull Create New Post a hyperlink that leads to the post creation page

bull Manage Posts a hyperlink that leads to the post management page

bull Logout a link button that would log out the current user

521 Creating UserMenu Class

We create the UserMenu class to represent the logic part of the user menu portlet Theclass is saved in the file wwwrootblogprotectedcomponentsUserMenuphp which has thefollowing content

Yiiimport(rsquoziiwidgetsCPortletrsquo)

class UserMenu extends CPortlet

public function init()

$this-gttitle=CHtmlencode(Yiiapp()-gtuser-gtname)

parentinit()

42 5 Portlets

protected function renderContent()

$this-gtrender(rsquouserMenursquo)

The UserMenu class extends from the CPortlet class that we created previously It overridesboth the init() method and the renderContent() method of CPortlet The former setsthe portlet title to be the name of the current user the latter generates the portlet bodycontent by rendering a view named userMenu

Tip Notice that we have to explicitly include the CPortlet class by calling Yii

import() before we refer to it the first time This is because CPortlet is part of thezii project ndash the official extension library for Yii For performance considerationclasses in this project are not listed as core classes Therefore we have to import itbefore we use it the first time

522 Creating userMenu View

Next we create the userMenu view which is saved in the file wwwrootblogprotected

componentsviewsuserMenuphp

ltulgt

ltligtltphp echo CHtmllink(rsquoCreate New Postrsquoarray(rsquopostcreatersquo)) gtltligt

ltligtltphp echo CHtmllink(rsquoManage Postsrsquoarray(rsquopostadminrsquo)) gtltligt

ltligtltphp echo CHtmllink(rsquoApprove Commentsrsquoarray(rsquocommentindexrsquo))

rsquo (rsquo Commentmodel()-gtpendingCommentCount rsquo)rsquo gtltligt

ltligtltphp echo CHtmllink(rsquoLogoutrsquoarray(rsquositelogoutrsquo)) gtltligt

ltulgt

Info By default view files for a widget should be placed under the views sub-directory of the directory containing the widget class file The file name must bethe same as the view name

523 Using UserMenu Portlet

It is time for us to make use of our newly completed UserMenu portlet We modify thelayout view file wwwrootblogprotectedviewslayoutscolumn2php as follows

53 Creating Tag Cloud Portlet 43

ltdiv id=sidebargt

ltphp if(Yiiapp()-gtuser-gtisGuest) $this-gtwidget(rsquoUserMenursquo) gt

ltdivgt

In the above we call the widget() method to generate and execute an instance of theUserMenu class Because the portlet should only be displayed to authenticated users weonly call widget() when the isGuest property of the current user is false (meaning theuser is authenticated)

524 Testing UserMenu Portlet

Letrsquos test what we have so far

1 Open a browser window and enter the URL httpwwwexamplecomblogindex

php Verify that there is nothing displayed in the side bar section of the page

2 Click on the Login hyperlink and fill out the login form to login If successful verifythat the UserMenu portlet appears in the side bar and the portlet has the usernameas its title

3 Click on the rsquoLogoutrsquo hyperlink in the UserMenu portlet Verify that the logout actionis successful and the UserMenu portlet disappears

525 Summary

What we have created is a portlet that is highly reusable We can easily reuse it in adifferent project with little or no modification Moreover the design of this portlet followsclosely the philosophy that logic and presentation should be separated While we did notpoint this out in the previous sections such practice is used nearly everywhere in a typicalYii application

53 Creating Tag Cloud Portlet

Tag cloud displays a list of post tags with visual decorations hinting the popularity of eachindividual tag

531 Creating TagCloud Class

We create the TagCloud class in the file wwwrootblogprotectedcomponentsTagCloud

php The file has the following content

44 5 Portlets

Yiiimport(rsquoziiwidgetsCPortletrsquo)

class TagCloud extends CPortlet

public $title=rsquoTagsrsquo

public $maxTags=20

protected function renderContent()

$tags=Tagmodel()-gtfindTagWeights($this-gtmaxTags)

foreach($tags as $tag=gt$weight)

$link=CHtmllink(CHtmlencode($tag) array(rsquopostindexrsquorsquotagrsquo=gt$tag))

echo CHtmltag(rsquospanrsquo array(

rsquoclassrsquo=gtrsquotagrsquo

rsquostylersquo=gtfont-size$weightpt) $link)n

Unlike the UserMenu portlet the TagCloud portlet does not use a view Instead its presen-tation is done in the renderContent() method This is because the presentation does notcontain much HTML tags

We display each tag as a hyperlink to the post index page with the corresponding tagparameter The font size of each tag link is adjusted according to their relative weightamong other tags If a tag has higher frequency value than the other it will have a biggerfont size

532 Using TagCloud Portlet

Usage of the TagCloud portlet is very simple We modify the layout file wwwrootblog

protectedviewslayoutsmainphp as follows

ltdiv id=sidebargt

ltphp if(Yiiapp()-gtuser-gtisGuest) $this-gtwidget(rsquoUserMenursquo) gt

ltphp $this-gtwidget(rsquoTagCloudrsquo array(

rsquomaxTagsrsquo=gtYiiapp()-gtparams[rsquotagCloudCountrsquo]

)) gt

ltdivgt

54 Creating Recent Comments Portlet 45

54 Creating Recent Comments Portlet

In this section we create the last portlet that displays a list of comments recently pub-lished

541 Creating RecentComments Class

We create the RecentComments class in the file wwwrootblogprotectedcomponentsRecentCommentsphp The file has the following content

Yiiimport(rsquoziiwidgetsCPortletrsquo)

class RecentComments extends CPortlet

public $title=rsquoRecent Commentsrsquo

public $maxComments=10

public function getRecentComments()

return Commentmodel()-gtfindRecentComments($this-gtmaxComments)

protected function renderContent()

$this-gtrender(rsquorecentCommentsrsquo)

In the above we invoke the findRecentComments method which is defined in the Comment

class as follows

class Comment extends CActiveRecord

public function findRecentComments($limit=10)

return $this-gtwith(rsquopostrsquo)-gtfindAll(array(

rsquoconditionrsquo=gtrsquotstatus=rsquoselfSTATUS APPROVED

rsquoorderrsquo=gtrsquotcreate time DESCrsquo

rsquolimitrsquo=gt$limit

))

46 5 Portlets

542 Creating recentComments View

The recentComments view is saved in the file wwwrootblogprotectedcomponentsviews

recentCommentsphp It simply displays every comment returned by the RecentComments

getRecentComments() method

543 Using RecentComments Portlet

We modify the layout file wwwrootblogprotectedviewslayoutsmainphp to embedthis last portlet

ltdiv id=sidebargt

ltphp if(Yiiapp()-gtuser-gtisGuest) $this-gtwidget(rsquoUserMenursquo) gt

ltphp $this-gtwidget(rsquoTagCloudrsquo array(

rsquomaxTagsrsquo=gtYiiapp()-gtparams[rsquotagCloudCountrsquo]

)) gt

ltphp $this-gtwidget(rsquoRecentCommentsrsquo array(

rsquomaxCommentsrsquo=gtYiiapp()-gtparams[rsquorecentCommentCountrsquo]

)) gt

ltdivgt

Chapter 6

Final Work

61 Beautifying URLs

The URLs linking various pages of our blog application currently look ugly For examplethe URL for the page showing a post looks like the following

indexphpr=postshowampid=1

In this section we describe how to beautify these URLs and make them SEO-friendlyOur goal is to be able to use the following URLs in the application

1 indexphppostsyii leads to the page showing a list of posts with tag yii

2 indexphppost2A+Test+Post leads to the page showing the detail of the postwith ID 2 whose title is A Test Post

3 indexphppostupdate1 leads to the page that allows updating the post with ID1

Note that in the second URL format we include the post title in the URL This is mainlyto make the URL SEO friendly It is said that search engines may also respect the wordsfound in a URL when it is being indexed

To achieve our goal we modify the application configuration as follows

return array(

rsquocomponentsrsquo=gtarray(

rsquourlManagerrsquo=gtarray(

rsquourlFormatrsquo=gtrsquopathrsquo

rsquorulesrsquo=gtarray(

48 6 Final Work

rsquopostltidd+gtlttitlegtrsquo=gtrsquopostviewrsquorsquopostslttaggtrsquo=gtrsquopostindexrsquo

)

)

)

)

In the above we configure the urlManager component by setting its urlFormat propertyto be path and adding a set of rules

The rules are used by urlManager to parse and create the URLs in the desired formatFor example the first rule says that if a URL indexphppostsyii is requested theurlManager component should be responsible to dispatch the request to the route post

index and generate a tag GET parameter with the value yii On the other hand whencreating a URL with the route postindex and parameter tag the urlManager componentwill also use this rule to generate the desired URL indexphppostsyii For this reasonwe say that urlManager is a two-way URL manager

The urlManager component can further beautify our URLs such as hiding indexphp inthe URLs appending suffix like html to the URLs We can obtain these features easilyby configuring various properties of urlManager in the application configuration For moredetails please refer to the Guide

62 Logging Errors

A production Web application often needs sophisticated logging for various events In ourblog application we would like to log the errors occurring when it is being used Sucherrors could be programming mistakes or usersrsquo misuse of the system Logging these errorswill help us to improve the blog application

We enable the error logging by modifying the application configuration as follows

return array(

rsquopreloadrsquo=gtarray(rsquologrsquo)

rsquocomponentsrsquo=gtarray(

rsquologrsquo=gtarray(

rsquoclassrsquo=gtrsquoCLogRouterrsquo

rsquoroutesrsquo=gtarray(

array(

rsquoclassrsquo=gtrsquoCFileLogRoutersquo

rsquolevelsrsquo=gtrsquoerror warningrsquo

63 Final Tune-up and Deployment 49

)

)

)

)

)

With the above configuration if an error or warning occurs detailed information will belogged and saved in a file located under the directory wwwrootblogprotectedruntime

The log component offers more advanced features such as sending log messages to a listof email addresses displaying log messages in JavaScript console window etc For moredetails please refer to the Guide

63 Final Tune-up and Deployment

We are close to finish our blog application Before deployment we would like to do sometune-ups

631 Changing Home Page

We change to use the post list page as the home page We modify the application config-uration as follows

return array(

rsquodefaultControllerrsquo=gtrsquopostrsquo

)

Tip Because PostController already declares list to be its default action whenwe access the home page of the application we will see the result generated by thelist action of the post controller

632 Enabling Schema Caching

Because ActiveRecord relies on the metadata about tables to determine the column infor-mation it takes time to read the metadata and analyze it This may not be a problemduring development stage but for an application running in production mode it is a totalwaste of time if the database schema does not change Therefore we should enable theschema caching by modifying the application configuration as follows

50 6 Final Work

return array(

rsquocomponentsrsquo=gtarray(

rsquocachersquo=gtarray(

rsquoclassrsquo=gtrsquoCDbCachersquo

)

rsquodbrsquo=gtarray(

rsquoclassrsquo=gtrsquosystemdbCDbConnectionrsquo

rsquoconnectionStringrsquo=gtrsquosqlitewwwrootblogprotecteddatablogdbrsquo

rsquoschemaCachingDurationrsquo=gt3600

)

)

)

In the above we first add a cache component which uses a default SQLite database as thecaching storage If our server is equipped with other caching extensions such as APCwe could change to use them as well We also modify the db component by setting itsschemaCachingDuration property to be 3600 which means the parsed database schemadata can remain valid in cache for 3600 seconds

633 Disabling Debugging Mode

We modify the entry script file wwwrootblogindexphp by removing the line definingthe constant YII DEBUG This constant is useful during development stage because it allowsYii to display more debugging information when an error occurs However when theapplication is running in production mode displaying debugging information is not agood idea because it may contain sensitive information such as where the script file islocated and the content in the file etc

634 Deploying the Application

The final deployment process manly involves copying the directory wwwrootblog to thetarget directory The following checklist shows every needed step

1 Install Yii in the target place if it is not available

2 Copy the entire directory wwwrootblog to the target place

3 Edit the entry script file indexphp by pointing the $yii variable to the new Yiibootstrap file

4 Edit the file protectedyiicphp by setting the $yiic variable to be the new Yiiyiicphp file

64 Future Enhancements 51

5 Change the permission of the directories assets and protectedruntime so that theyare writable by the Web server process

64 Future Enhancements

641 Using a Theme

Without writing any code our blog application is already themeable To use a themewe mainly need to develop the theme by writing customized view files in the theme Forexample to use a theme named classic that uses a different page layout we would createa layout view file wwwrootblogthemesclassicviewslayoutsmainphp We also needto change the application configuration to indicate our choice of the classic theme

return array(

rsquothemersquo=gtrsquoclassicrsquo

)

642 Internationalization

We may also internationalize our blog application so that its pages can be displayed indifferent languages This mainly involves efforts in two aspects

First we may create view files in different languages For example for the list pageof PostController we can create a view file wwwrootblogprotectedviewspostzh cn

listphp When the application is configured to use simplified Chinese (the language codeis zh cn) Yii will automatically use this new view file instead of the original one

Second we may create message translations for those messages generated by code Themessage translations should be saved as files under the directory wwwrootblogprotected

messages We also need to modify the code where we use text strings by enclosing themin the method call Yiit()

For more details about internationalization please refer to the Guide

643 Improving Performance with Cache

While the Yii framework itself is very efficient it is not necessarily true that an applicationwritten in Yii efficient There are several places in our blog application that we can improvethe performance For example the tag clould portlet could be one of the performancebottlenecks because it involves complex database query and PHP logic

52 6 Final Work

We can make use of the sophisticated caching feature provided by Yii to improve theperformance One of the most useful components in Yii is COutputCache which caches afragment of page display so that the underlying code generating the fragment does not needto be executed for every request For example in the layout file wwwrootblogprotected

viewslayoutsmainphp we can enclose the tag cloud portlet with COutputCache

ltphp if($this-gtbeginCache(rsquotagCloudrsquo array(rsquodurationrsquo=gt3600))) gt

ltphp $this-gtwidget(rsquoTagCloudrsquo array(

rsquomaxTagsrsquo=gtYiiapp()-gtparams[rsquotagCloudCountrsquo]

)) gt

ltphp $this-gtendCache() gt

With the above code the tag cloud display will be served from cache instead of beinggenerated on-the-fly for every request The cached content will remain valid in cache for3600 seconds

644 Adding New Features

Our blog application only has very basic functionalities To become a complete blogsystem more features are needed for example calendar portlet email notifications postcategorization archived post portlet and so on We will leave the implementation of thesefeatures to interested readers

  • Contents
  • License
  • Getting Started
    • Building a Blog System using Yii
    • Testdriving with Yii
      • Installing Yii
      • Creating Skeleton Application
      • Application Workflow
        • Requirements Analysis
        • Overall Design
          • Initial Prototyping
            • Setting Up Database
              • Creating Database
              • Establishing Database Connection
                • Scaffolding
                • Authenticating User
                • Summary
                  • Post Management
                    • Customizing Post Model
                      • Customizing rules() Method
                      • Customizing relations() Method
                      • Representing Status in Text
                        • Creating and Updating Posts
                          • Customizing Access Control
                          • Customizing create and update Operations
                            • Displaying Posts
                              • Customizing view Operation
                              • Customizing index Operation
                                • Managing Posts
                                  • Listing Posts in Tabular View
                                  • Deleting Posts
                                      • Comment Management
                                        • Customizing Comment Model
                                          • Customizing rules() Method
                                          • Customizing attributeLabels() Method
                                          • Customizing Saving Process
                                            • Creating and Displaying Comments
                                              • Displaying Comments
                                              • Creating Comments
                                                • Managing Comments
                                                  • Updating and Deleting Comments
                                                  • Approving Comments
                                                      • Portlets
                                                        • Customizing Page Layout
                                                        • Creating User Menu Portlet
                                                          • Creating UserMenu Class
                                                          • Creating userMenu View
                                                          • Using UserMenu Portlet
                                                          • Testing UserMenu Portlet
                                                          • Summary
                                                            • Creating Tag Cloud Portlet
                                                              • Creating TagCloud Class
                                                              • Using TagCloud Portlet
                                                                • Creating Recent Comments Portlet
                                                                  • Creating RecentComments Class
                                                                  • Creating recentComments View
                                                                  • Using RecentComments Portlet
                                                                      • Final Work
                                                                        • Beautifying URLs
                                                                        • Logging Errors
                                                                        • Final Tune-up and Deployment
                                                                          • Changing Home Page
                                                                          • Enabling Schema Caching
                                                                          • Disabling Debugging Mode
                                                                          • Deploying the Application
                                                                            • Future Enhancements
                                                                              • Using a Theme
                                                                              • Internationalization
                                                                              • Improving Performance with Cache
                                                                              • Adding New Features
Page 37: Yii Blog 1.1.0 Demo Tutor

30 3 Post Management

public function actionDelete()

if(Yiiapp()-gtrequest-gtisPostRequest)

we only allow deletion via POST request

$this-gtloadModel()-gtdelete()

if(isset($ POST[rsquoajaxrsquo]))

$this-gtredirect(array(rsquoindexrsquo))

else

throw new CHttpException(400rsquoInvalid request Please do not repeat this request againrsquo)

The above code is the one generated by the yiic tool without any change We wouldlike to explain a little bit more about the checking on $ POST[rsquoajaxrsquo] The [CGridView]widget has a very nice feature that its sorting pagination and deletion operations are alldone in AJAX mode by default That means the whole page does not get reloaded if anyof the above operations is performed However it is also possible that the widget runs innon-AJAX mode (by setting its ajaxUpdate property to be false or disabling JavaScript onthe client side) It is necessary for the delete action to differentiate these two scenarios ifthe delete request is made via AJAX we should not redirect the user browser otherwisewe should

Deleting a post should also cause the deletion of all comments for that post In additionwe should also update the tbl tag table regarding the tags for the deleted post Both ofthese tasks can be achieved by writing an afterDelete method in the Post model class asfollows

protected function afterDelete()

parentafterDelete()

Commentmodel()-gtdeleteAll(rsquopost id=rsquo$this-gtid)

Tagmodel()-gtupdateFrequency($this-gttags rsquorsquo)

The above code is very straightforward it first deletes all those comments whose post id

is the same as the ID of the deleted post it then updates the tbl tag table for the tags

of the deleted post

34 Managing Posts 31

Tip We have to explicitly delete all comments for the deleted post here becauseSQLite does not really support foreign key constraints In a DBMS that supportsthis constraint (such as MySQL PostgreSQL) the foreign key constraint can be setup such that the DBMS automatically deletes the related comments if the post isdeleted In that case we no longer this explicit deletion call in our code

32 3 Post Management

Chapter 4

Comment Management

41 Customizing Comment Model

For the Comment model we mainly need to customize the rules() and attributeLabels()

methods The attributeLabels() method returns a mapping between attribute namesand attribute labels We do not need to touch relations() since the code generated bythe yiic tool is good enough

411 Customizing rules() Method

We first customize the validation rules generated by the yiic tool The following rules areused for comments

public function rules()

return array(

array(rsquocontent author emailrsquo rsquorequiredrsquo)

array(rsquoauthor email urlrsquo rsquolengthrsquo rsquomaxrsquo=gt128)

array(rsquoemailrsquorsquoemailrsquo)

array(rsquourlrsquorsquourlrsquo)

)

In the above we specify that the author email and content attributes are required thelength of author email and url cannot exceed 128 the email attribute must be a validemail address and the url attribute must be a valid URL

412 Customizing attributeLabels() Method

We then customize the attributeLabels() method to declare the label display for eachmodel attribute This method returns an array consisting of name-label pairs When wecall CHtmlactiveLabel() to display an attribute label

34 4 Comment Management

public function attributeLabels()

return array(

rsquoidrsquo =gt rsquoIdrsquo

rsquocontentrsquo =gt rsquoCommentrsquo

rsquostatusrsquo =gt rsquoStatusrsquo

rsquocreate timersquo =gt rsquoCreate Timersquo

rsquoauthorrsquo =gt rsquoNamersquo

rsquoemailrsquo =gt rsquoEmailrsquo

rsquourlrsquo =gt rsquoWebsitersquo

rsquopost idrsquo =gt rsquoPostrsquo

)

Tip If the label for an attribute is not declared in attributeLabels() an algo-rithm will be used to generate an appropriate label For example a label VerifyCode will be generated for attributes verify code or verifyCode

413 Customizing Saving Process

Because we want to record the creation time of a comment we override the beforeSave()

method of Comment like we do for the Post model

protected function beforeSave()

if(parentbeforeSave())

if($this-gtisNewRecord)

$this-gtcreate time=time()

return true

else

return false

42 Creating and Displaying Comments

In this section we implement the comment display and creation features

421 Displaying Comments

Instead of displaying and creating comments on individual pages we use the post detailpage (generated by the view action of PostController) Below the post content display

42 Creating and Displaying Comments 35

we display first a list of comments belonging to that post and then a comment creationform

In order to display comments on the post detail page we modify the view script wwwrootblogprotectedviewspostviewphp as follows

ltdiv id=commentsgt

ltphp if($model-gtcommentCountgt=1) gt

lth3gt

ltphp echo $model-gtcommentCount rsquocomment(s)rsquo gt

lth3gt

ltphp $this-gtrenderPartial(rsquo commentsrsquoarray(

rsquopostrsquo=gt$model

rsquocommentsrsquo=gt$model-gtcomments

)) gt

ltphp endif gt

ltdivgt

In the above we call renderPartial() to render a partial view named comments to displaythe list of comments belonging to the current post Note that in the view we use theexpression $model-gtcomments to retrieve the comments for the post This is valid becausewe have declared a comments relation in the Post class Evaluating this expression wouldtrigger an implicit JOIN database query to bring back the proper comments This featureis known as lazy relational query

The partial view comments is not very interesting It mainly goes through every commentand displays the detail of it Interested readers may refer to wwwrootyiidemosblog

protectedpost commentsphp

422 Creating Comments

To handle comment creation we first modify the actionView() method of PostController

as follows

public function actionView()

$post=$this-gtloadModel()

$comment=$this-gtnewComment($post)

$this-gtrender(rsquoviewrsquoarray(

rsquomodelrsquo=gt$post

rsquocommentrsquo=gt$comment

))

36 4 Comment Management

protected function newComment($post)

$comment=new Comment

if(isset($ POST[rsquoCommentrsquo]))

$comment-gtattributes=$ POST[rsquoCommentrsquo]

if($post-gtaddComment($comment))

if($comment-gtstatus==CommentSTATUS PENDING)

Yiiapp()-gtuser-gtsetFlash(rsquocommentSubmittedrsquorsquoThank yoursquo)

$this-gtrefresh()

return $comment

In the above we call the newComment() method before we render view In the newComment()

method we generate a Comment instance and check if the comment form is submitted If sowe try to add the comment for the post by calling $post-gtaddComment($comment) If it goesthrough we refresh the post detail page In case the comment needs to be approved we willshow a flash message to indicate this decision A flash message is usually a confirmationmessage displayed to end users If the user clicks on the refresh button of his browser themessage will disappear

We also need to modify wwwrootblogprotectedviewspostviewphp furthermore

ltdiv id=commentsgt

lth3gtLeave a Commentlth3gt

ltphp if(Yiiapp()-gtuser-gthasFlash(rsquocommentSubmittedrsquo)) gt

ltdiv class=successgt

ltphp echo Yiiapp()-gtuser-gtgetFlash(rsquocommentSubmittedrsquo) gt

ltdivgt

ltphp else gt

ltphp $this-gtrenderPartial(rsquocomment formrsquoarray(

rsquomodelrsquo=gt$comment

)) gt

ltphp endif gt

ltdivgtlt-- comments --gt

43 Managing Comments 37

In the above code we display the flash message if it is available If not we displaythe comment input form by rendering the partial view wwwrootblogprotectedviews

comment formphp

43 Managing Comments

Comment management includes updating deleting and approving comments These op-erations are implemented as actions in the CommentController class

431 Updating and Deleting Comments

The code generated by yiic for updating and deleting comments remains largely un-changed

432 Approving Comments

When comments are newly created they are in pending approval status and need to beapproved in order to be visible to guest users Approving a comment is mainly aboutchanging the status column of the comment

We create an actionApprove() method in CommentController as follows

public function actionApprove()

if(Yiiapp()-gtrequest-gtisPostRequest)

$comment=$this-gtloadModel()

$comment-gtapprove()

$this-gtredirect(array(rsquoindexrsquo))

else

throw new CHttpException(400rsquoInvalid requestrsquo)

In the above when the approve action is invoked via a POST request we call the approve()

method defined in the Comment model to change the status We then redirect the userbrowser to the page displaying the post that this comment belongs to

We also modify the actionIndex() method of Comment to show all comments We wouldlike to see comments pending approved to show up first

public function actionIndex()

38 4 Comment Management

$dataProvider=new CActiveDataProvider(rsquoCommentrsquo array(

rsquocriteriarsquo=gtarray(

rsquowithrsquo=gtrsquopostrsquo

rsquoorderrsquo=gtrsquotstatus tcreate time DESCrsquo

)

))

$this-gtrender(rsquoindexrsquoarray(

rsquodataProviderrsquo=gt$dataProvider

))

Notice that in the above code because both tbl post and tbl comment have columns statusand create time we need to disambiguate the corresponding column reference by prefixingthem with table alias names As described in the guide the alias for the primary table ina relational query is always t Therefore we are prefixing t to the status and create time

columns in the above code

Like the post index view the index view for CommentController uses [CListView] to displaythe comment list which in turn uses the partial view wwwrootblogprotectedviews

comment viewphp to display the detail of each individual comment We will not go intodetails here Interested readers may refer to the corresponding file in the blog demowwwrootyiidemosblogprotectedviewscomment viewphp

Chapter 5

Portlets

51 Customizing Page Layout

Features like rdquothe most recent commentsrdquo rdquotag cloudrdquo are better to be implemented inportlets A portlet is a pluggable user interface component that renders a fragment ofHTML code In this section we describe how to set up the portlet architecture for ourblog application

Before we go into details about developing portlets we first need to adjust our page layoutso that the portlets can be placed in a side bar

A page layout determines how to share common sections (eg header footer) amongdifferent pages and how to embed contents that are specific for different pages In Yiia page layout is represented as a view under the protectedviewslayouts folder Whenwe call the render() method in a controller the rendering result will be automaticallyembedded into this layout view before it is displayed to end users The default layoutview is named as main which means the wwwrootblogprotectedviewslayoutsmain

php file in our blog application

We could directly modify the main layout to add a side bar However doing so wouldmake some irrelevant pages such as about contact pages to have a side bar which is notalways desirable For this reason we need different layouts for different pages

We thus create two different layouts column1 and column2 The former displays the pagein a single column while the latter displays two columns in the page one of which is theside bar We will use column2 layout for the post and comment pages and column1 forthe rest of the pages Because both column1 and column2 still share the same header andfooter sections we let them to share the main layout

Based on the above description we create the column1 layout as follows

ltphp $this-gtbeginContent(rsquolayoutsmainrsquo) gt

ltdiv class=containergt

40 5 Portlets

ltdiv id=contentgt

ltphp echo $content gt

ltdivgtlt-- content --gt

ltdivgt

ltphp $this-gtendContent() gt

and the column2 layout as follows

ltphp $this-gtbeginContent(rsquolayoutsmainrsquo) gt

ltdiv class=containergt

ltdiv class=span-18gt

ltdiv id=contentgt

ltphp echo $content gt

ltdivgtlt-- content --gt

ltdivgt

ltdiv class=span-6 lastgt

ltdiv id=sidebargt

portlets to be placed here

ltdivgtlt-- sidebar --gt

ltdivgt

ltdivgt

ltphp $this-gtendContent() gt

As we can see both column1 and column2 are enclosed in a pair of beginContent andendContent calls They essentially use the specified view layoutsmain to decorate theenclosed content In all these layout views the variable $content refers to the dynamiccontent to be embedded into the layout

Besides customizing the layout view file we also need to adjust the CSS file wwwroot

blogcssmaincss so that the overall appearance would look like what we see in the blogdemo We will not go into details here

Info The skeleton application generated by the yiic tool uses the BluePrint CSSframework It provides a nice font reset and a robust grid system For more detailson how to use this framework please refer to its documentation

After creating the new layouts we need to set the layout property of controllers to makeuse of them So far we have three controllers SiteController PostController andCommentController We set SiteController to use column1 as the default layout whilethe rest two of the controllers use column2 for example

class PostController extends Controller

52 Creating User Menu Portlet 41

public $layout=rsquocolumn2rsquo

Tip If some view of the controller does not use the same layout as declaredby the layout property it may customize it in the view by a simple assignment$this-gtlayout=rsquonewlayoutrsquo

52 Creating User Menu Portlet

Based on the requirements analysis we need three different portlets the rdquouser menurdquoportlet the rdquotag cloudrdquo portlet and the rdquorecent commentsrdquo portlet We will develop theseportlets by extending the [CPortlet] widget provided by Yii

In this section we will develop our first concrete portlet - the user menu portlet whichdisplays a list of menu items that are only available to authenticated users The menucontains four items

bull Approve Comments a hyperlink that leads to a list of comments pending approval

bull Create New Post a hyperlink that leads to the post creation page

bull Manage Posts a hyperlink that leads to the post management page

bull Logout a link button that would log out the current user

521 Creating UserMenu Class

We create the UserMenu class to represent the logic part of the user menu portlet Theclass is saved in the file wwwrootblogprotectedcomponentsUserMenuphp which has thefollowing content

Yiiimport(rsquoziiwidgetsCPortletrsquo)

class UserMenu extends CPortlet

public function init()

$this-gttitle=CHtmlencode(Yiiapp()-gtuser-gtname)

parentinit()

42 5 Portlets

protected function renderContent()

$this-gtrender(rsquouserMenursquo)

The UserMenu class extends from the CPortlet class that we created previously It overridesboth the init() method and the renderContent() method of CPortlet The former setsthe portlet title to be the name of the current user the latter generates the portlet bodycontent by rendering a view named userMenu

Tip Notice that we have to explicitly include the CPortlet class by calling Yii

import() before we refer to it the first time This is because CPortlet is part of thezii project ndash the official extension library for Yii For performance considerationclasses in this project are not listed as core classes Therefore we have to import itbefore we use it the first time

522 Creating userMenu View

Next we create the userMenu view which is saved in the file wwwrootblogprotected

componentsviewsuserMenuphp

ltulgt

ltligtltphp echo CHtmllink(rsquoCreate New Postrsquoarray(rsquopostcreatersquo)) gtltligt

ltligtltphp echo CHtmllink(rsquoManage Postsrsquoarray(rsquopostadminrsquo)) gtltligt

ltligtltphp echo CHtmllink(rsquoApprove Commentsrsquoarray(rsquocommentindexrsquo))

rsquo (rsquo Commentmodel()-gtpendingCommentCount rsquo)rsquo gtltligt

ltligtltphp echo CHtmllink(rsquoLogoutrsquoarray(rsquositelogoutrsquo)) gtltligt

ltulgt

Info By default view files for a widget should be placed under the views sub-directory of the directory containing the widget class file The file name must bethe same as the view name

523 Using UserMenu Portlet

It is time for us to make use of our newly completed UserMenu portlet We modify thelayout view file wwwrootblogprotectedviewslayoutscolumn2php as follows

53 Creating Tag Cloud Portlet 43

ltdiv id=sidebargt

ltphp if(Yiiapp()-gtuser-gtisGuest) $this-gtwidget(rsquoUserMenursquo) gt

ltdivgt

In the above we call the widget() method to generate and execute an instance of theUserMenu class Because the portlet should only be displayed to authenticated users weonly call widget() when the isGuest property of the current user is false (meaning theuser is authenticated)

524 Testing UserMenu Portlet

Letrsquos test what we have so far

1 Open a browser window and enter the URL httpwwwexamplecomblogindex

php Verify that there is nothing displayed in the side bar section of the page

2 Click on the Login hyperlink and fill out the login form to login If successful verifythat the UserMenu portlet appears in the side bar and the portlet has the usernameas its title

3 Click on the rsquoLogoutrsquo hyperlink in the UserMenu portlet Verify that the logout actionis successful and the UserMenu portlet disappears

525 Summary

What we have created is a portlet that is highly reusable We can easily reuse it in adifferent project with little or no modification Moreover the design of this portlet followsclosely the philosophy that logic and presentation should be separated While we did notpoint this out in the previous sections such practice is used nearly everywhere in a typicalYii application

53 Creating Tag Cloud Portlet

Tag cloud displays a list of post tags with visual decorations hinting the popularity of eachindividual tag

531 Creating TagCloud Class

We create the TagCloud class in the file wwwrootblogprotectedcomponentsTagCloud

php The file has the following content

44 5 Portlets

Yiiimport(rsquoziiwidgetsCPortletrsquo)

class TagCloud extends CPortlet

public $title=rsquoTagsrsquo

public $maxTags=20

protected function renderContent()

$tags=Tagmodel()-gtfindTagWeights($this-gtmaxTags)

foreach($tags as $tag=gt$weight)

$link=CHtmllink(CHtmlencode($tag) array(rsquopostindexrsquorsquotagrsquo=gt$tag))

echo CHtmltag(rsquospanrsquo array(

rsquoclassrsquo=gtrsquotagrsquo

rsquostylersquo=gtfont-size$weightpt) $link)n

Unlike the UserMenu portlet the TagCloud portlet does not use a view Instead its presen-tation is done in the renderContent() method This is because the presentation does notcontain much HTML tags

We display each tag as a hyperlink to the post index page with the corresponding tagparameter The font size of each tag link is adjusted according to their relative weightamong other tags If a tag has higher frequency value than the other it will have a biggerfont size

532 Using TagCloud Portlet

Usage of the TagCloud portlet is very simple We modify the layout file wwwrootblog

protectedviewslayoutsmainphp as follows

ltdiv id=sidebargt

ltphp if(Yiiapp()-gtuser-gtisGuest) $this-gtwidget(rsquoUserMenursquo) gt

ltphp $this-gtwidget(rsquoTagCloudrsquo array(

rsquomaxTagsrsquo=gtYiiapp()-gtparams[rsquotagCloudCountrsquo]

)) gt

ltdivgt

54 Creating Recent Comments Portlet 45

54 Creating Recent Comments Portlet

In this section we create the last portlet that displays a list of comments recently pub-lished

541 Creating RecentComments Class

We create the RecentComments class in the file wwwrootblogprotectedcomponentsRecentCommentsphp The file has the following content

Yiiimport(rsquoziiwidgetsCPortletrsquo)

class RecentComments extends CPortlet

public $title=rsquoRecent Commentsrsquo

public $maxComments=10

public function getRecentComments()

return Commentmodel()-gtfindRecentComments($this-gtmaxComments)

protected function renderContent()

$this-gtrender(rsquorecentCommentsrsquo)

In the above we invoke the findRecentComments method which is defined in the Comment

class as follows

class Comment extends CActiveRecord

public function findRecentComments($limit=10)

return $this-gtwith(rsquopostrsquo)-gtfindAll(array(

rsquoconditionrsquo=gtrsquotstatus=rsquoselfSTATUS APPROVED

rsquoorderrsquo=gtrsquotcreate time DESCrsquo

rsquolimitrsquo=gt$limit

))

46 5 Portlets

542 Creating recentComments View

The recentComments view is saved in the file wwwrootblogprotectedcomponentsviews

recentCommentsphp It simply displays every comment returned by the RecentComments

getRecentComments() method

543 Using RecentComments Portlet

We modify the layout file wwwrootblogprotectedviewslayoutsmainphp to embedthis last portlet

ltdiv id=sidebargt

ltphp if(Yiiapp()-gtuser-gtisGuest) $this-gtwidget(rsquoUserMenursquo) gt

ltphp $this-gtwidget(rsquoTagCloudrsquo array(

rsquomaxTagsrsquo=gtYiiapp()-gtparams[rsquotagCloudCountrsquo]

)) gt

ltphp $this-gtwidget(rsquoRecentCommentsrsquo array(

rsquomaxCommentsrsquo=gtYiiapp()-gtparams[rsquorecentCommentCountrsquo]

)) gt

ltdivgt

Chapter 6

Final Work

61 Beautifying URLs

The URLs linking various pages of our blog application currently look ugly For examplethe URL for the page showing a post looks like the following

indexphpr=postshowampid=1

In this section we describe how to beautify these URLs and make them SEO-friendlyOur goal is to be able to use the following URLs in the application

1 indexphppostsyii leads to the page showing a list of posts with tag yii

2 indexphppost2A+Test+Post leads to the page showing the detail of the postwith ID 2 whose title is A Test Post

3 indexphppostupdate1 leads to the page that allows updating the post with ID1

Note that in the second URL format we include the post title in the URL This is mainlyto make the URL SEO friendly It is said that search engines may also respect the wordsfound in a URL when it is being indexed

To achieve our goal we modify the application configuration as follows

return array(

rsquocomponentsrsquo=gtarray(

rsquourlManagerrsquo=gtarray(

rsquourlFormatrsquo=gtrsquopathrsquo

rsquorulesrsquo=gtarray(

48 6 Final Work

rsquopostltidd+gtlttitlegtrsquo=gtrsquopostviewrsquorsquopostslttaggtrsquo=gtrsquopostindexrsquo

)

)

)

)

In the above we configure the urlManager component by setting its urlFormat propertyto be path and adding a set of rules

The rules are used by urlManager to parse and create the URLs in the desired formatFor example the first rule says that if a URL indexphppostsyii is requested theurlManager component should be responsible to dispatch the request to the route post

index and generate a tag GET parameter with the value yii On the other hand whencreating a URL with the route postindex and parameter tag the urlManager componentwill also use this rule to generate the desired URL indexphppostsyii For this reasonwe say that urlManager is a two-way URL manager

The urlManager component can further beautify our URLs such as hiding indexphp inthe URLs appending suffix like html to the URLs We can obtain these features easilyby configuring various properties of urlManager in the application configuration For moredetails please refer to the Guide

62 Logging Errors

A production Web application often needs sophisticated logging for various events In ourblog application we would like to log the errors occurring when it is being used Sucherrors could be programming mistakes or usersrsquo misuse of the system Logging these errorswill help us to improve the blog application

We enable the error logging by modifying the application configuration as follows

return array(

rsquopreloadrsquo=gtarray(rsquologrsquo)

rsquocomponentsrsquo=gtarray(

rsquologrsquo=gtarray(

rsquoclassrsquo=gtrsquoCLogRouterrsquo

rsquoroutesrsquo=gtarray(

array(

rsquoclassrsquo=gtrsquoCFileLogRoutersquo

rsquolevelsrsquo=gtrsquoerror warningrsquo

63 Final Tune-up and Deployment 49

)

)

)

)

)

With the above configuration if an error or warning occurs detailed information will belogged and saved in a file located under the directory wwwrootblogprotectedruntime

The log component offers more advanced features such as sending log messages to a listof email addresses displaying log messages in JavaScript console window etc For moredetails please refer to the Guide

63 Final Tune-up and Deployment

We are close to finish our blog application Before deployment we would like to do sometune-ups

631 Changing Home Page

We change to use the post list page as the home page We modify the application config-uration as follows

return array(

rsquodefaultControllerrsquo=gtrsquopostrsquo

)

Tip Because PostController already declares list to be its default action whenwe access the home page of the application we will see the result generated by thelist action of the post controller

632 Enabling Schema Caching

Because ActiveRecord relies on the metadata about tables to determine the column infor-mation it takes time to read the metadata and analyze it This may not be a problemduring development stage but for an application running in production mode it is a totalwaste of time if the database schema does not change Therefore we should enable theschema caching by modifying the application configuration as follows

50 6 Final Work

return array(

rsquocomponentsrsquo=gtarray(

rsquocachersquo=gtarray(

rsquoclassrsquo=gtrsquoCDbCachersquo

)

rsquodbrsquo=gtarray(

rsquoclassrsquo=gtrsquosystemdbCDbConnectionrsquo

rsquoconnectionStringrsquo=gtrsquosqlitewwwrootblogprotecteddatablogdbrsquo

rsquoschemaCachingDurationrsquo=gt3600

)

)

)

In the above we first add a cache component which uses a default SQLite database as thecaching storage If our server is equipped with other caching extensions such as APCwe could change to use them as well We also modify the db component by setting itsschemaCachingDuration property to be 3600 which means the parsed database schemadata can remain valid in cache for 3600 seconds

633 Disabling Debugging Mode

We modify the entry script file wwwrootblogindexphp by removing the line definingthe constant YII DEBUG This constant is useful during development stage because it allowsYii to display more debugging information when an error occurs However when theapplication is running in production mode displaying debugging information is not agood idea because it may contain sensitive information such as where the script file islocated and the content in the file etc

634 Deploying the Application

The final deployment process manly involves copying the directory wwwrootblog to thetarget directory The following checklist shows every needed step

1 Install Yii in the target place if it is not available

2 Copy the entire directory wwwrootblog to the target place

3 Edit the entry script file indexphp by pointing the $yii variable to the new Yiibootstrap file

4 Edit the file protectedyiicphp by setting the $yiic variable to be the new Yiiyiicphp file

64 Future Enhancements 51

5 Change the permission of the directories assets and protectedruntime so that theyare writable by the Web server process

64 Future Enhancements

641 Using a Theme

Without writing any code our blog application is already themeable To use a themewe mainly need to develop the theme by writing customized view files in the theme Forexample to use a theme named classic that uses a different page layout we would createa layout view file wwwrootblogthemesclassicviewslayoutsmainphp We also needto change the application configuration to indicate our choice of the classic theme

return array(

rsquothemersquo=gtrsquoclassicrsquo

)

642 Internationalization

We may also internationalize our blog application so that its pages can be displayed indifferent languages This mainly involves efforts in two aspects

First we may create view files in different languages For example for the list pageof PostController we can create a view file wwwrootblogprotectedviewspostzh cn

listphp When the application is configured to use simplified Chinese (the language codeis zh cn) Yii will automatically use this new view file instead of the original one

Second we may create message translations for those messages generated by code Themessage translations should be saved as files under the directory wwwrootblogprotected

messages We also need to modify the code where we use text strings by enclosing themin the method call Yiit()

For more details about internationalization please refer to the Guide

643 Improving Performance with Cache

While the Yii framework itself is very efficient it is not necessarily true that an applicationwritten in Yii efficient There are several places in our blog application that we can improvethe performance For example the tag clould portlet could be one of the performancebottlenecks because it involves complex database query and PHP logic

52 6 Final Work

We can make use of the sophisticated caching feature provided by Yii to improve theperformance One of the most useful components in Yii is COutputCache which caches afragment of page display so that the underlying code generating the fragment does not needto be executed for every request For example in the layout file wwwrootblogprotected

viewslayoutsmainphp we can enclose the tag cloud portlet with COutputCache

ltphp if($this-gtbeginCache(rsquotagCloudrsquo array(rsquodurationrsquo=gt3600))) gt

ltphp $this-gtwidget(rsquoTagCloudrsquo array(

rsquomaxTagsrsquo=gtYiiapp()-gtparams[rsquotagCloudCountrsquo]

)) gt

ltphp $this-gtendCache() gt

With the above code the tag cloud display will be served from cache instead of beinggenerated on-the-fly for every request The cached content will remain valid in cache for3600 seconds

644 Adding New Features

Our blog application only has very basic functionalities To become a complete blogsystem more features are needed for example calendar portlet email notifications postcategorization archived post portlet and so on We will leave the implementation of thesefeatures to interested readers

  • Contents
  • License
  • Getting Started
    • Building a Blog System using Yii
    • Testdriving with Yii
      • Installing Yii
      • Creating Skeleton Application
      • Application Workflow
        • Requirements Analysis
        • Overall Design
          • Initial Prototyping
            • Setting Up Database
              • Creating Database
              • Establishing Database Connection
                • Scaffolding
                • Authenticating User
                • Summary
                  • Post Management
                    • Customizing Post Model
                      • Customizing rules() Method
                      • Customizing relations() Method
                      • Representing Status in Text
                        • Creating and Updating Posts
                          • Customizing Access Control
                          • Customizing create and update Operations
                            • Displaying Posts
                              • Customizing view Operation
                              • Customizing index Operation
                                • Managing Posts
                                  • Listing Posts in Tabular View
                                  • Deleting Posts
                                      • Comment Management
                                        • Customizing Comment Model
                                          • Customizing rules() Method
                                          • Customizing attributeLabels() Method
                                          • Customizing Saving Process
                                            • Creating and Displaying Comments
                                              • Displaying Comments
                                              • Creating Comments
                                                • Managing Comments
                                                  • Updating and Deleting Comments
                                                  • Approving Comments
                                                      • Portlets
                                                        • Customizing Page Layout
                                                        • Creating User Menu Portlet
                                                          • Creating UserMenu Class
                                                          • Creating userMenu View
                                                          • Using UserMenu Portlet
                                                          • Testing UserMenu Portlet
                                                          • Summary
                                                            • Creating Tag Cloud Portlet
                                                              • Creating TagCloud Class
                                                              • Using TagCloud Portlet
                                                                • Creating Recent Comments Portlet
                                                                  • Creating RecentComments Class
                                                                  • Creating recentComments View
                                                                  • Using RecentComments Portlet
                                                                      • Final Work
                                                                        • Beautifying URLs
                                                                        • Logging Errors
                                                                        • Final Tune-up and Deployment
                                                                          • Changing Home Page
                                                                          • Enabling Schema Caching
                                                                          • Disabling Debugging Mode
                                                                          • Deploying the Application
                                                                            • Future Enhancements
                                                                              • Using a Theme
                                                                              • Internationalization
                                                                              • Improving Performance with Cache
                                                                              • Adding New Features
Page 38: Yii Blog 1.1.0 Demo Tutor

34 Managing Posts 31

Tip We have to explicitly delete all comments for the deleted post here becauseSQLite does not really support foreign key constraints In a DBMS that supportsthis constraint (such as MySQL PostgreSQL) the foreign key constraint can be setup such that the DBMS automatically deletes the related comments if the post isdeleted In that case we no longer this explicit deletion call in our code

32 3 Post Management

Chapter 4

Comment Management

41 Customizing Comment Model

For the Comment model we mainly need to customize the rules() and attributeLabels()

methods The attributeLabels() method returns a mapping between attribute namesand attribute labels We do not need to touch relations() since the code generated bythe yiic tool is good enough

411 Customizing rules() Method

We first customize the validation rules generated by the yiic tool The following rules areused for comments

public function rules()

return array(

array(rsquocontent author emailrsquo rsquorequiredrsquo)

array(rsquoauthor email urlrsquo rsquolengthrsquo rsquomaxrsquo=gt128)

array(rsquoemailrsquorsquoemailrsquo)

array(rsquourlrsquorsquourlrsquo)

)

In the above we specify that the author email and content attributes are required thelength of author email and url cannot exceed 128 the email attribute must be a validemail address and the url attribute must be a valid URL

412 Customizing attributeLabels() Method

We then customize the attributeLabels() method to declare the label display for eachmodel attribute This method returns an array consisting of name-label pairs When wecall CHtmlactiveLabel() to display an attribute label

34 4 Comment Management

public function attributeLabels()

return array(

rsquoidrsquo =gt rsquoIdrsquo

rsquocontentrsquo =gt rsquoCommentrsquo

rsquostatusrsquo =gt rsquoStatusrsquo

rsquocreate timersquo =gt rsquoCreate Timersquo

rsquoauthorrsquo =gt rsquoNamersquo

rsquoemailrsquo =gt rsquoEmailrsquo

rsquourlrsquo =gt rsquoWebsitersquo

rsquopost idrsquo =gt rsquoPostrsquo

)

Tip If the label for an attribute is not declared in attributeLabels() an algo-rithm will be used to generate an appropriate label For example a label VerifyCode will be generated for attributes verify code or verifyCode

413 Customizing Saving Process

Because we want to record the creation time of a comment we override the beforeSave()

method of Comment like we do for the Post model

protected function beforeSave()

if(parentbeforeSave())

if($this-gtisNewRecord)

$this-gtcreate time=time()

return true

else

return false

42 Creating and Displaying Comments

In this section we implement the comment display and creation features

421 Displaying Comments

Instead of displaying and creating comments on individual pages we use the post detailpage (generated by the view action of PostController) Below the post content display

42 Creating and Displaying Comments 35

we display first a list of comments belonging to that post and then a comment creationform

In order to display comments on the post detail page we modify the view script wwwrootblogprotectedviewspostviewphp as follows

ltdiv id=commentsgt

ltphp if($model-gtcommentCountgt=1) gt

lth3gt

ltphp echo $model-gtcommentCount rsquocomment(s)rsquo gt

lth3gt

ltphp $this-gtrenderPartial(rsquo commentsrsquoarray(

rsquopostrsquo=gt$model

rsquocommentsrsquo=gt$model-gtcomments

)) gt

ltphp endif gt

ltdivgt

In the above we call renderPartial() to render a partial view named comments to displaythe list of comments belonging to the current post Note that in the view we use theexpression $model-gtcomments to retrieve the comments for the post This is valid becausewe have declared a comments relation in the Post class Evaluating this expression wouldtrigger an implicit JOIN database query to bring back the proper comments This featureis known as lazy relational query

The partial view comments is not very interesting It mainly goes through every commentand displays the detail of it Interested readers may refer to wwwrootyiidemosblog

protectedpost commentsphp

422 Creating Comments

To handle comment creation we first modify the actionView() method of PostController

as follows

public function actionView()

$post=$this-gtloadModel()

$comment=$this-gtnewComment($post)

$this-gtrender(rsquoviewrsquoarray(

rsquomodelrsquo=gt$post

rsquocommentrsquo=gt$comment

))

36 4 Comment Management

protected function newComment($post)

$comment=new Comment

if(isset($ POST[rsquoCommentrsquo]))

$comment-gtattributes=$ POST[rsquoCommentrsquo]

if($post-gtaddComment($comment))

if($comment-gtstatus==CommentSTATUS PENDING)

Yiiapp()-gtuser-gtsetFlash(rsquocommentSubmittedrsquorsquoThank yoursquo)

$this-gtrefresh()

return $comment

In the above we call the newComment() method before we render view In the newComment()

method we generate a Comment instance and check if the comment form is submitted If sowe try to add the comment for the post by calling $post-gtaddComment($comment) If it goesthrough we refresh the post detail page In case the comment needs to be approved we willshow a flash message to indicate this decision A flash message is usually a confirmationmessage displayed to end users If the user clicks on the refresh button of his browser themessage will disappear

We also need to modify wwwrootblogprotectedviewspostviewphp furthermore

ltdiv id=commentsgt

lth3gtLeave a Commentlth3gt

ltphp if(Yiiapp()-gtuser-gthasFlash(rsquocommentSubmittedrsquo)) gt

ltdiv class=successgt

ltphp echo Yiiapp()-gtuser-gtgetFlash(rsquocommentSubmittedrsquo) gt

ltdivgt

ltphp else gt

ltphp $this-gtrenderPartial(rsquocomment formrsquoarray(

rsquomodelrsquo=gt$comment

)) gt

ltphp endif gt

ltdivgtlt-- comments --gt

43 Managing Comments 37

In the above code we display the flash message if it is available If not we displaythe comment input form by rendering the partial view wwwrootblogprotectedviews

comment formphp

43 Managing Comments

Comment management includes updating deleting and approving comments These op-erations are implemented as actions in the CommentController class

431 Updating and Deleting Comments

The code generated by yiic for updating and deleting comments remains largely un-changed

432 Approving Comments

When comments are newly created they are in pending approval status and need to beapproved in order to be visible to guest users Approving a comment is mainly aboutchanging the status column of the comment

We create an actionApprove() method in CommentController as follows

public function actionApprove()

if(Yiiapp()-gtrequest-gtisPostRequest)

$comment=$this-gtloadModel()

$comment-gtapprove()

$this-gtredirect(array(rsquoindexrsquo))

else

throw new CHttpException(400rsquoInvalid requestrsquo)

In the above when the approve action is invoked via a POST request we call the approve()

method defined in the Comment model to change the status We then redirect the userbrowser to the page displaying the post that this comment belongs to

We also modify the actionIndex() method of Comment to show all comments We wouldlike to see comments pending approved to show up first

public function actionIndex()

38 4 Comment Management

$dataProvider=new CActiveDataProvider(rsquoCommentrsquo array(

rsquocriteriarsquo=gtarray(

rsquowithrsquo=gtrsquopostrsquo

rsquoorderrsquo=gtrsquotstatus tcreate time DESCrsquo

)

))

$this-gtrender(rsquoindexrsquoarray(

rsquodataProviderrsquo=gt$dataProvider

))

Notice that in the above code because both tbl post and tbl comment have columns statusand create time we need to disambiguate the corresponding column reference by prefixingthem with table alias names As described in the guide the alias for the primary table ina relational query is always t Therefore we are prefixing t to the status and create time

columns in the above code

Like the post index view the index view for CommentController uses [CListView] to displaythe comment list which in turn uses the partial view wwwrootblogprotectedviews

comment viewphp to display the detail of each individual comment We will not go intodetails here Interested readers may refer to the corresponding file in the blog demowwwrootyiidemosblogprotectedviewscomment viewphp

Chapter 5

Portlets

51 Customizing Page Layout

Features like rdquothe most recent commentsrdquo rdquotag cloudrdquo are better to be implemented inportlets A portlet is a pluggable user interface component that renders a fragment ofHTML code In this section we describe how to set up the portlet architecture for ourblog application

Before we go into details about developing portlets we first need to adjust our page layoutso that the portlets can be placed in a side bar

A page layout determines how to share common sections (eg header footer) amongdifferent pages and how to embed contents that are specific for different pages In Yiia page layout is represented as a view under the protectedviewslayouts folder Whenwe call the render() method in a controller the rendering result will be automaticallyembedded into this layout view before it is displayed to end users The default layoutview is named as main which means the wwwrootblogprotectedviewslayoutsmain

php file in our blog application

We could directly modify the main layout to add a side bar However doing so wouldmake some irrelevant pages such as about contact pages to have a side bar which is notalways desirable For this reason we need different layouts for different pages

We thus create two different layouts column1 and column2 The former displays the pagein a single column while the latter displays two columns in the page one of which is theside bar We will use column2 layout for the post and comment pages and column1 forthe rest of the pages Because both column1 and column2 still share the same header andfooter sections we let them to share the main layout

Based on the above description we create the column1 layout as follows

ltphp $this-gtbeginContent(rsquolayoutsmainrsquo) gt

ltdiv class=containergt

40 5 Portlets

ltdiv id=contentgt

ltphp echo $content gt

ltdivgtlt-- content --gt

ltdivgt

ltphp $this-gtendContent() gt

and the column2 layout as follows

ltphp $this-gtbeginContent(rsquolayoutsmainrsquo) gt

ltdiv class=containergt

ltdiv class=span-18gt

ltdiv id=contentgt

ltphp echo $content gt

ltdivgtlt-- content --gt

ltdivgt

ltdiv class=span-6 lastgt

ltdiv id=sidebargt

portlets to be placed here

ltdivgtlt-- sidebar --gt

ltdivgt

ltdivgt

ltphp $this-gtendContent() gt

As we can see both column1 and column2 are enclosed in a pair of beginContent andendContent calls They essentially use the specified view layoutsmain to decorate theenclosed content In all these layout views the variable $content refers to the dynamiccontent to be embedded into the layout

Besides customizing the layout view file we also need to adjust the CSS file wwwroot

blogcssmaincss so that the overall appearance would look like what we see in the blogdemo We will not go into details here

Info The skeleton application generated by the yiic tool uses the BluePrint CSSframework It provides a nice font reset and a robust grid system For more detailson how to use this framework please refer to its documentation

After creating the new layouts we need to set the layout property of controllers to makeuse of them So far we have three controllers SiteController PostController andCommentController We set SiteController to use column1 as the default layout whilethe rest two of the controllers use column2 for example

class PostController extends Controller

52 Creating User Menu Portlet 41

public $layout=rsquocolumn2rsquo

Tip If some view of the controller does not use the same layout as declaredby the layout property it may customize it in the view by a simple assignment$this-gtlayout=rsquonewlayoutrsquo

52 Creating User Menu Portlet

Based on the requirements analysis we need three different portlets the rdquouser menurdquoportlet the rdquotag cloudrdquo portlet and the rdquorecent commentsrdquo portlet We will develop theseportlets by extending the [CPortlet] widget provided by Yii

In this section we will develop our first concrete portlet - the user menu portlet whichdisplays a list of menu items that are only available to authenticated users The menucontains four items

bull Approve Comments a hyperlink that leads to a list of comments pending approval

bull Create New Post a hyperlink that leads to the post creation page

bull Manage Posts a hyperlink that leads to the post management page

bull Logout a link button that would log out the current user

521 Creating UserMenu Class

We create the UserMenu class to represent the logic part of the user menu portlet Theclass is saved in the file wwwrootblogprotectedcomponentsUserMenuphp which has thefollowing content

Yiiimport(rsquoziiwidgetsCPortletrsquo)

class UserMenu extends CPortlet

public function init()

$this-gttitle=CHtmlencode(Yiiapp()-gtuser-gtname)

parentinit()

42 5 Portlets

protected function renderContent()

$this-gtrender(rsquouserMenursquo)

The UserMenu class extends from the CPortlet class that we created previously It overridesboth the init() method and the renderContent() method of CPortlet The former setsthe portlet title to be the name of the current user the latter generates the portlet bodycontent by rendering a view named userMenu

Tip Notice that we have to explicitly include the CPortlet class by calling Yii

import() before we refer to it the first time This is because CPortlet is part of thezii project ndash the official extension library for Yii For performance considerationclasses in this project are not listed as core classes Therefore we have to import itbefore we use it the first time

522 Creating userMenu View

Next we create the userMenu view which is saved in the file wwwrootblogprotected

componentsviewsuserMenuphp

ltulgt

ltligtltphp echo CHtmllink(rsquoCreate New Postrsquoarray(rsquopostcreatersquo)) gtltligt

ltligtltphp echo CHtmllink(rsquoManage Postsrsquoarray(rsquopostadminrsquo)) gtltligt

ltligtltphp echo CHtmllink(rsquoApprove Commentsrsquoarray(rsquocommentindexrsquo))

rsquo (rsquo Commentmodel()-gtpendingCommentCount rsquo)rsquo gtltligt

ltligtltphp echo CHtmllink(rsquoLogoutrsquoarray(rsquositelogoutrsquo)) gtltligt

ltulgt

Info By default view files for a widget should be placed under the views sub-directory of the directory containing the widget class file The file name must bethe same as the view name

523 Using UserMenu Portlet

It is time for us to make use of our newly completed UserMenu portlet We modify thelayout view file wwwrootblogprotectedviewslayoutscolumn2php as follows

53 Creating Tag Cloud Portlet 43

ltdiv id=sidebargt

ltphp if(Yiiapp()-gtuser-gtisGuest) $this-gtwidget(rsquoUserMenursquo) gt

ltdivgt

In the above we call the widget() method to generate and execute an instance of theUserMenu class Because the portlet should only be displayed to authenticated users weonly call widget() when the isGuest property of the current user is false (meaning theuser is authenticated)

524 Testing UserMenu Portlet

Letrsquos test what we have so far

1 Open a browser window and enter the URL httpwwwexamplecomblogindex

php Verify that there is nothing displayed in the side bar section of the page

2 Click on the Login hyperlink and fill out the login form to login If successful verifythat the UserMenu portlet appears in the side bar and the portlet has the usernameas its title

3 Click on the rsquoLogoutrsquo hyperlink in the UserMenu portlet Verify that the logout actionis successful and the UserMenu portlet disappears

525 Summary

What we have created is a portlet that is highly reusable We can easily reuse it in adifferent project with little or no modification Moreover the design of this portlet followsclosely the philosophy that logic and presentation should be separated While we did notpoint this out in the previous sections such practice is used nearly everywhere in a typicalYii application

53 Creating Tag Cloud Portlet

Tag cloud displays a list of post tags with visual decorations hinting the popularity of eachindividual tag

531 Creating TagCloud Class

We create the TagCloud class in the file wwwrootblogprotectedcomponentsTagCloud

php The file has the following content

44 5 Portlets

Yiiimport(rsquoziiwidgetsCPortletrsquo)

class TagCloud extends CPortlet

public $title=rsquoTagsrsquo

public $maxTags=20

protected function renderContent()

$tags=Tagmodel()-gtfindTagWeights($this-gtmaxTags)

foreach($tags as $tag=gt$weight)

$link=CHtmllink(CHtmlencode($tag) array(rsquopostindexrsquorsquotagrsquo=gt$tag))

echo CHtmltag(rsquospanrsquo array(

rsquoclassrsquo=gtrsquotagrsquo

rsquostylersquo=gtfont-size$weightpt) $link)n

Unlike the UserMenu portlet the TagCloud portlet does not use a view Instead its presen-tation is done in the renderContent() method This is because the presentation does notcontain much HTML tags

We display each tag as a hyperlink to the post index page with the corresponding tagparameter The font size of each tag link is adjusted according to their relative weightamong other tags If a tag has higher frequency value than the other it will have a biggerfont size

532 Using TagCloud Portlet

Usage of the TagCloud portlet is very simple We modify the layout file wwwrootblog

protectedviewslayoutsmainphp as follows

ltdiv id=sidebargt

ltphp if(Yiiapp()-gtuser-gtisGuest) $this-gtwidget(rsquoUserMenursquo) gt

ltphp $this-gtwidget(rsquoTagCloudrsquo array(

rsquomaxTagsrsquo=gtYiiapp()-gtparams[rsquotagCloudCountrsquo]

)) gt

ltdivgt

54 Creating Recent Comments Portlet 45

54 Creating Recent Comments Portlet

In this section we create the last portlet that displays a list of comments recently pub-lished

541 Creating RecentComments Class

We create the RecentComments class in the file wwwrootblogprotectedcomponentsRecentCommentsphp The file has the following content

Yiiimport(rsquoziiwidgetsCPortletrsquo)

class RecentComments extends CPortlet

public $title=rsquoRecent Commentsrsquo

public $maxComments=10

public function getRecentComments()

return Commentmodel()-gtfindRecentComments($this-gtmaxComments)

protected function renderContent()

$this-gtrender(rsquorecentCommentsrsquo)

In the above we invoke the findRecentComments method which is defined in the Comment

class as follows

class Comment extends CActiveRecord

public function findRecentComments($limit=10)

return $this-gtwith(rsquopostrsquo)-gtfindAll(array(

rsquoconditionrsquo=gtrsquotstatus=rsquoselfSTATUS APPROVED

rsquoorderrsquo=gtrsquotcreate time DESCrsquo

rsquolimitrsquo=gt$limit

))

46 5 Portlets

542 Creating recentComments View

The recentComments view is saved in the file wwwrootblogprotectedcomponentsviews

recentCommentsphp It simply displays every comment returned by the RecentComments

getRecentComments() method

543 Using RecentComments Portlet

We modify the layout file wwwrootblogprotectedviewslayoutsmainphp to embedthis last portlet

ltdiv id=sidebargt

ltphp if(Yiiapp()-gtuser-gtisGuest) $this-gtwidget(rsquoUserMenursquo) gt

ltphp $this-gtwidget(rsquoTagCloudrsquo array(

rsquomaxTagsrsquo=gtYiiapp()-gtparams[rsquotagCloudCountrsquo]

)) gt

ltphp $this-gtwidget(rsquoRecentCommentsrsquo array(

rsquomaxCommentsrsquo=gtYiiapp()-gtparams[rsquorecentCommentCountrsquo]

)) gt

ltdivgt

Chapter 6

Final Work

61 Beautifying URLs

The URLs linking various pages of our blog application currently look ugly For examplethe URL for the page showing a post looks like the following

indexphpr=postshowampid=1

In this section we describe how to beautify these URLs and make them SEO-friendlyOur goal is to be able to use the following URLs in the application

1 indexphppostsyii leads to the page showing a list of posts with tag yii

2 indexphppost2A+Test+Post leads to the page showing the detail of the postwith ID 2 whose title is A Test Post

3 indexphppostupdate1 leads to the page that allows updating the post with ID1

Note that in the second URL format we include the post title in the URL This is mainlyto make the URL SEO friendly It is said that search engines may also respect the wordsfound in a URL when it is being indexed

To achieve our goal we modify the application configuration as follows

return array(

rsquocomponentsrsquo=gtarray(

rsquourlManagerrsquo=gtarray(

rsquourlFormatrsquo=gtrsquopathrsquo

rsquorulesrsquo=gtarray(

48 6 Final Work

rsquopostltidd+gtlttitlegtrsquo=gtrsquopostviewrsquorsquopostslttaggtrsquo=gtrsquopostindexrsquo

)

)

)

)

In the above we configure the urlManager component by setting its urlFormat propertyto be path and adding a set of rules

The rules are used by urlManager to parse and create the URLs in the desired formatFor example the first rule says that if a URL indexphppostsyii is requested theurlManager component should be responsible to dispatch the request to the route post

index and generate a tag GET parameter with the value yii On the other hand whencreating a URL with the route postindex and parameter tag the urlManager componentwill also use this rule to generate the desired URL indexphppostsyii For this reasonwe say that urlManager is a two-way URL manager

The urlManager component can further beautify our URLs such as hiding indexphp inthe URLs appending suffix like html to the URLs We can obtain these features easilyby configuring various properties of urlManager in the application configuration For moredetails please refer to the Guide

62 Logging Errors

A production Web application often needs sophisticated logging for various events In ourblog application we would like to log the errors occurring when it is being used Sucherrors could be programming mistakes or usersrsquo misuse of the system Logging these errorswill help us to improve the blog application

We enable the error logging by modifying the application configuration as follows

return array(

rsquopreloadrsquo=gtarray(rsquologrsquo)

rsquocomponentsrsquo=gtarray(

rsquologrsquo=gtarray(

rsquoclassrsquo=gtrsquoCLogRouterrsquo

rsquoroutesrsquo=gtarray(

array(

rsquoclassrsquo=gtrsquoCFileLogRoutersquo

rsquolevelsrsquo=gtrsquoerror warningrsquo

63 Final Tune-up and Deployment 49

)

)

)

)

)

With the above configuration if an error or warning occurs detailed information will belogged and saved in a file located under the directory wwwrootblogprotectedruntime

The log component offers more advanced features such as sending log messages to a listof email addresses displaying log messages in JavaScript console window etc For moredetails please refer to the Guide

63 Final Tune-up and Deployment

We are close to finish our blog application Before deployment we would like to do sometune-ups

631 Changing Home Page

We change to use the post list page as the home page We modify the application config-uration as follows

return array(

rsquodefaultControllerrsquo=gtrsquopostrsquo

)

Tip Because PostController already declares list to be its default action whenwe access the home page of the application we will see the result generated by thelist action of the post controller

632 Enabling Schema Caching

Because ActiveRecord relies on the metadata about tables to determine the column infor-mation it takes time to read the metadata and analyze it This may not be a problemduring development stage but for an application running in production mode it is a totalwaste of time if the database schema does not change Therefore we should enable theschema caching by modifying the application configuration as follows

50 6 Final Work

return array(

rsquocomponentsrsquo=gtarray(

rsquocachersquo=gtarray(

rsquoclassrsquo=gtrsquoCDbCachersquo

)

rsquodbrsquo=gtarray(

rsquoclassrsquo=gtrsquosystemdbCDbConnectionrsquo

rsquoconnectionStringrsquo=gtrsquosqlitewwwrootblogprotecteddatablogdbrsquo

rsquoschemaCachingDurationrsquo=gt3600

)

)

)

In the above we first add a cache component which uses a default SQLite database as thecaching storage If our server is equipped with other caching extensions such as APCwe could change to use them as well We also modify the db component by setting itsschemaCachingDuration property to be 3600 which means the parsed database schemadata can remain valid in cache for 3600 seconds

633 Disabling Debugging Mode

We modify the entry script file wwwrootblogindexphp by removing the line definingthe constant YII DEBUG This constant is useful during development stage because it allowsYii to display more debugging information when an error occurs However when theapplication is running in production mode displaying debugging information is not agood idea because it may contain sensitive information such as where the script file islocated and the content in the file etc

634 Deploying the Application

The final deployment process manly involves copying the directory wwwrootblog to thetarget directory The following checklist shows every needed step

1 Install Yii in the target place if it is not available

2 Copy the entire directory wwwrootblog to the target place

3 Edit the entry script file indexphp by pointing the $yii variable to the new Yiibootstrap file

4 Edit the file protectedyiicphp by setting the $yiic variable to be the new Yiiyiicphp file

64 Future Enhancements 51

5 Change the permission of the directories assets and protectedruntime so that theyare writable by the Web server process

64 Future Enhancements

641 Using a Theme

Without writing any code our blog application is already themeable To use a themewe mainly need to develop the theme by writing customized view files in the theme Forexample to use a theme named classic that uses a different page layout we would createa layout view file wwwrootblogthemesclassicviewslayoutsmainphp We also needto change the application configuration to indicate our choice of the classic theme

return array(

rsquothemersquo=gtrsquoclassicrsquo

)

642 Internationalization

We may also internationalize our blog application so that its pages can be displayed indifferent languages This mainly involves efforts in two aspects

First we may create view files in different languages For example for the list pageof PostController we can create a view file wwwrootblogprotectedviewspostzh cn

listphp When the application is configured to use simplified Chinese (the language codeis zh cn) Yii will automatically use this new view file instead of the original one

Second we may create message translations for those messages generated by code Themessage translations should be saved as files under the directory wwwrootblogprotected

messages We also need to modify the code where we use text strings by enclosing themin the method call Yiit()

For more details about internationalization please refer to the Guide

643 Improving Performance with Cache

While the Yii framework itself is very efficient it is not necessarily true that an applicationwritten in Yii efficient There are several places in our blog application that we can improvethe performance For example the tag clould portlet could be one of the performancebottlenecks because it involves complex database query and PHP logic

52 6 Final Work

We can make use of the sophisticated caching feature provided by Yii to improve theperformance One of the most useful components in Yii is COutputCache which caches afragment of page display so that the underlying code generating the fragment does not needto be executed for every request For example in the layout file wwwrootblogprotected

viewslayoutsmainphp we can enclose the tag cloud portlet with COutputCache

ltphp if($this-gtbeginCache(rsquotagCloudrsquo array(rsquodurationrsquo=gt3600))) gt

ltphp $this-gtwidget(rsquoTagCloudrsquo array(

rsquomaxTagsrsquo=gtYiiapp()-gtparams[rsquotagCloudCountrsquo]

)) gt

ltphp $this-gtendCache() gt

With the above code the tag cloud display will be served from cache instead of beinggenerated on-the-fly for every request The cached content will remain valid in cache for3600 seconds

644 Adding New Features

Our blog application only has very basic functionalities To become a complete blogsystem more features are needed for example calendar portlet email notifications postcategorization archived post portlet and so on We will leave the implementation of thesefeatures to interested readers

  • Contents
  • License
  • Getting Started
    • Building a Blog System using Yii
    • Testdriving with Yii
      • Installing Yii
      • Creating Skeleton Application
      • Application Workflow
        • Requirements Analysis
        • Overall Design
          • Initial Prototyping
            • Setting Up Database
              • Creating Database
              • Establishing Database Connection
                • Scaffolding
                • Authenticating User
                • Summary
                  • Post Management
                    • Customizing Post Model
                      • Customizing rules() Method
                      • Customizing relations() Method
                      • Representing Status in Text
                        • Creating and Updating Posts
                          • Customizing Access Control
                          • Customizing create and update Operations
                            • Displaying Posts
                              • Customizing view Operation
                              • Customizing index Operation
                                • Managing Posts
                                  • Listing Posts in Tabular View
                                  • Deleting Posts
                                      • Comment Management
                                        • Customizing Comment Model
                                          • Customizing rules() Method
                                          • Customizing attributeLabels() Method
                                          • Customizing Saving Process
                                            • Creating and Displaying Comments
                                              • Displaying Comments
                                              • Creating Comments
                                                • Managing Comments
                                                  • Updating and Deleting Comments
                                                  • Approving Comments
                                                      • Portlets
                                                        • Customizing Page Layout
                                                        • Creating User Menu Portlet
                                                          • Creating UserMenu Class
                                                          • Creating userMenu View
                                                          • Using UserMenu Portlet
                                                          • Testing UserMenu Portlet
                                                          • Summary
                                                            • Creating Tag Cloud Portlet
                                                              • Creating TagCloud Class
                                                              • Using TagCloud Portlet
                                                                • Creating Recent Comments Portlet
                                                                  • Creating RecentComments Class
                                                                  • Creating recentComments View
                                                                  • Using RecentComments Portlet
                                                                      • Final Work
                                                                        • Beautifying URLs
                                                                        • Logging Errors
                                                                        • Final Tune-up and Deployment
                                                                          • Changing Home Page
                                                                          • Enabling Schema Caching
                                                                          • Disabling Debugging Mode
                                                                          • Deploying the Application
                                                                            • Future Enhancements
                                                                              • Using a Theme
                                                                              • Internationalization
                                                                              • Improving Performance with Cache
                                                                              • Adding New Features
Page 39: Yii Blog 1.1.0 Demo Tutor

32 3 Post Management

Chapter 4

Comment Management

41 Customizing Comment Model

For the Comment model we mainly need to customize the rules() and attributeLabels()

methods The attributeLabels() method returns a mapping between attribute namesand attribute labels We do not need to touch relations() since the code generated bythe yiic tool is good enough

411 Customizing rules() Method

We first customize the validation rules generated by the yiic tool The following rules areused for comments

public function rules()

return array(

array(rsquocontent author emailrsquo rsquorequiredrsquo)

array(rsquoauthor email urlrsquo rsquolengthrsquo rsquomaxrsquo=gt128)

array(rsquoemailrsquorsquoemailrsquo)

array(rsquourlrsquorsquourlrsquo)

)

In the above we specify that the author email and content attributes are required thelength of author email and url cannot exceed 128 the email attribute must be a validemail address and the url attribute must be a valid URL

412 Customizing attributeLabels() Method

We then customize the attributeLabels() method to declare the label display for eachmodel attribute This method returns an array consisting of name-label pairs When wecall CHtmlactiveLabel() to display an attribute label

34 4 Comment Management

public function attributeLabels()

return array(

rsquoidrsquo =gt rsquoIdrsquo

rsquocontentrsquo =gt rsquoCommentrsquo

rsquostatusrsquo =gt rsquoStatusrsquo

rsquocreate timersquo =gt rsquoCreate Timersquo

rsquoauthorrsquo =gt rsquoNamersquo

rsquoemailrsquo =gt rsquoEmailrsquo

rsquourlrsquo =gt rsquoWebsitersquo

rsquopost idrsquo =gt rsquoPostrsquo

)

Tip If the label for an attribute is not declared in attributeLabels() an algo-rithm will be used to generate an appropriate label For example a label VerifyCode will be generated for attributes verify code or verifyCode

413 Customizing Saving Process

Because we want to record the creation time of a comment we override the beforeSave()

method of Comment like we do for the Post model

protected function beforeSave()

if(parentbeforeSave())

if($this-gtisNewRecord)

$this-gtcreate time=time()

return true

else

return false

42 Creating and Displaying Comments

In this section we implement the comment display and creation features

421 Displaying Comments

Instead of displaying and creating comments on individual pages we use the post detailpage (generated by the view action of PostController) Below the post content display

42 Creating and Displaying Comments 35

we display first a list of comments belonging to that post and then a comment creationform

In order to display comments on the post detail page we modify the view script wwwrootblogprotectedviewspostviewphp as follows

ltdiv id=commentsgt

ltphp if($model-gtcommentCountgt=1) gt

lth3gt

ltphp echo $model-gtcommentCount rsquocomment(s)rsquo gt

lth3gt

ltphp $this-gtrenderPartial(rsquo commentsrsquoarray(

rsquopostrsquo=gt$model

rsquocommentsrsquo=gt$model-gtcomments

)) gt

ltphp endif gt

ltdivgt

In the above we call renderPartial() to render a partial view named comments to displaythe list of comments belonging to the current post Note that in the view we use theexpression $model-gtcomments to retrieve the comments for the post This is valid becausewe have declared a comments relation in the Post class Evaluating this expression wouldtrigger an implicit JOIN database query to bring back the proper comments This featureis known as lazy relational query

The partial view comments is not very interesting It mainly goes through every commentand displays the detail of it Interested readers may refer to wwwrootyiidemosblog

protectedpost commentsphp

422 Creating Comments

To handle comment creation we first modify the actionView() method of PostController

as follows

public function actionView()

$post=$this-gtloadModel()

$comment=$this-gtnewComment($post)

$this-gtrender(rsquoviewrsquoarray(

rsquomodelrsquo=gt$post

rsquocommentrsquo=gt$comment

))

36 4 Comment Management

protected function newComment($post)

$comment=new Comment

if(isset($ POST[rsquoCommentrsquo]))

$comment-gtattributes=$ POST[rsquoCommentrsquo]

if($post-gtaddComment($comment))

if($comment-gtstatus==CommentSTATUS PENDING)

Yiiapp()-gtuser-gtsetFlash(rsquocommentSubmittedrsquorsquoThank yoursquo)

$this-gtrefresh()

return $comment

In the above we call the newComment() method before we render view In the newComment()

method we generate a Comment instance and check if the comment form is submitted If sowe try to add the comment for the post by calling $post-gtaddComment($comment) If it goesthrough we refresh the post detail page In case the comment needs to be approved we willshow a flash message to indicate this decision A flash message is usually a confirmationmessage displayed to end users If the user clicks on the refresh button of his browser themessage will disappear

We also need to modify wwwrootblogprotectedviewspostviewphp furthermore

ltdiv id=commentsgt

lth3gtLeave a Commentlth3gt

ltphp if(Yiiapp()-gtuser-gthasFlash(rsquocommentSubmittedrsquo)) gt

ltdiv class=successgt

ltphp echo Yiiapp()-gtuser-gtgetFlash(rsquocommentSubmittedrsquo) gt

ltdivgt

ltphp else gt

ltphp $this-gtrenderPartial(rsquocomment formrsquoarray(

rsquomodelrsquo=gt$comment

)) gt

ltphp endif gt

ltdivgtlt-- comments --gt

43 Managing Comments 37

In the above code we display the flash message if it is available If not we displaythe comment input form by rendering the partial view wwwrootblogprotectedviews

comment formphp

43 Managing Comments

Comment management includes updating deleting and approving comments These op-erations are implemented as actions in the CommentController class

431 Updating and Deleting Comments

The code generated by yiic for updating and deleting comments remains largely un-changed

432 Approving Comments

When comments are newly created they are in pending approval status and need to beapproved in order to be visible to guest users Approving a comment is mainly aboutchanging the status column of the comment

We create an actionApprove() method in CommentController as follows

public function actionApprove()

if(Yiiapp()-gtrequest-gtisPostRequest)

$comment=$this-gtloadModel()

$comment-gtapprove()

$this-gtredirect(array(rsquoindexrsquo))

else

throw new CHttpException(400rsquoInvalid requestrsquo)

In the above when the approve action is invoked via a POST request we call the approve()

method defined in the Comment model to change the status We then redirect the userbrowser to the page displaying the post that this comment belongs to

We also modify the actionIndex() method of Comment to show all comments We wouldlike to see comments pending approved to show up first

public function actionIndex()

38 4 Comment Management

$dataProvider=new CActiveDataProvider(rsquoCommentrsquo array(

rsquocriteriarsquo=gtarray(

rsquowithrsquo=gtrsquopostrsquo

rsquoorderrsquo=gtrsquotstatus tcreate time DESCrsquo

)

))

$this-gtrender(rsquoindexrsquoarray(

rsquodataProviderrsquo=gt$dataProvider

))

Notice that in the above code because both tbl post and tbl comment have columns statusand create time we need to disambiguate the corresponding column reference by prefixingthem with table alias names As described in the guide the alias for the primary table ina relational query is always t Therefore we are prefixing t to the status and create time

columns in the above code

Like the post index view the index view for CommentController uses [CListView] to displaythe comment list which in turn uses the partial view wwwrootblogprotectedviews

comment viewphp to display the detail of each individual comment We will not go intodetails here Interested readers may refer to the corresponding file in the blog demowwwrootyiidemosblogprotectedviewscomment viewphp

Chapter 5

Portlets

51 Customizing Page Layout

Features like rdquothe most recent commentsrdquo rdquotag cloudrdquo are better to be implemented inportlets A portlet is a pluggable user interface component that renders a fragment ofHTML code In this section we describe how to set up the portlet architecture for ourblog application

Before we go into details about developing portlets we first need to adjust our page layoutso that the portlets can be placed in a side bar

A page layout determines how to share common sections (eg header footer) amongdifferent pages and how to embed contents that are specific for different pages In Yiia page layout is represented as a view under the protectedviewslayouts folder Whenwe call the render() method in a controller the rendering result will be automaticallyembedded into this layout view before it is displayed to end users The default layoutview is named as main which means the wwwrootblogprotectedviewslayoutsmain

php file in our blog application

We could directly modify the main layout to add a side bar However doing so wouldmake some irrelevant pages such as about contact pages to have a side bar which is notalways desirable For this reason we need different layouts for different pages

We thus create two different layouts column1 and column2 The former displays the pagein a single column while the latter displays two columns in the page one of which is theside bar We will use column2 layout for the post and comment pages and column1 forthe rest of the pages Because both column1 and column2 still share the same header andfooter sections we let them to share the main layout

Based on the above description we create the column1 layout as follows

ltphp $this-gtbeginContent(rsquolayoutsmainrsquo) gt

ltdiv class=containergt

40 5 Portlets

ltdiv id=contentgt

ltphp echo $content gt

ltdivgtlt-- content --gt

ltdivgt

ltphp $this-gtendContent() gt

and the column2 layout as follows

ltphp $this-gtbeginContent(rsquolayoutsmainrsquo) gt

ltdiv class=containergt

ltdiv class=span-18gt

ltdiv id=contentgt

ltphp echo $content gt

ltdivgtlt-- content --gt

ltdivgt

ltdiv class=span-6 lastgt

ltdiv id=sidebargt

portlets to be placed here

ltdivgtlt-- sidebar --gt

ltdivgt

ltdivgt

ltphp $this-gtendContent() gt

As we can see both column1 and column2 are enclosed in a pair of beginContent andendContent calls They essentially use the specified view layoutsmain to decorate theenclosed content In all these layout views the variable $content refers to the dynamiccontent to be embedded into the layout

Besides customizing the layout view file we also need to adjust the CSS file wwwroot

blogcssmaincss so that the overall appearance would look like what we see in the blogdemo We will not go into details here

Info The skeleton application generated by the yiic tool uses the BluePrint CSSframework It provides a nice font reset and a robust grid system For more detailson how to use this framework please refer to its documentation

After creating the new layouts we need to set the layout property of controllers to makeuse of them So far we have three controllers SiteController PostController andCommentController We set SiteController to use column1 as the default layout whilethe rest two of the controllers use column2 for example

class PostController extends Controller

52 Creating User Menu Portlet 41

public $layout=rsquocolumn2rsquo

Tip If some view of the controller does not use the same layout as declaredby the layout property it may customize it in the view by a simple assignment$this-gtlayout=rsquonewlayoutrsquo

52 Creating User Menu Portlet

Based on the requirements analysis we need three different portlets the rdquouser menurdquoportlet the rdquotag cloudrdquo portlet and the rdquorecent commentsrdquo portlet We will develop theseportlets by extending the [CPortlet] widget provided by Yii

In this section we will develop our first concrete portlet - the user menu portlet whichdisplays a list of menu items that are only available to authenticated users The menucontains four items

bull Approve Comments a hyperlink that leads to a list of comments pending approval

bull Create New Post a hyperlink that leads to the post creation page

bull Manage Posts a hyperlink that leads to the post management page

bull Logout a link button that would log out the current user

521 Creating UserMenu Class

We create the UserMenu class to represent the logic part of the user menu portlet Theclass is saved in the file wwwrootblogprotectedcomponentsUserMenuphp which has thefollowing content

Yiiimport(rsquoziiwidgetsCPortletrsquo)

class UserMenu extends CPortlet

public function init()

$this-gttitle=CHtmlencode(Yiiapp()-gtuser-gtname)

parentinit()

42 5 Portlets

protected function renderContent()

$this-gtrender(rsquouserMenursquo)

The UserMenu class extends from the CPortlet class that we created previously It overridesboth the init() method and the renderContent() method of CPortlet The former setsthe portlet title to be the name of the current user the latter generates the portlet bodycontent by rendering a view named userMenu

Tip Notice that we have to explicitly include the CPortlet class by calling Yii

import() before we refer to it the first time This is because CPortlet is part of thezii project ndash the official extension library for Yii For performance considerationclasses in this project are not listed as core classes Therefore we have to import itbefore we use it the first time

522 Creating userMenu View

Next we create the userMenu view which is saved in the file wwwrootblogprotected

componentsviewsuserMenuphp

ltulgt

ltligtltphp echo CHtmllink(rsquoCreate New Postrsquoarray(rsquopostcreatersquo)) gtltligt

ltligtltphp echo CHtmllink(rsquoManage Postsrsquoarray(rsquopostadminrsquo)) gtltligt

ltligtltphp echo CHtmllink(rsquoApprove Commentsrsquoarray(rsquocommentindexrsquo))

rsquo (rsquo Commentmodel()-gtpendingCommentCount rsquo)rsquo gtltligt

ltligtltphp echo CHtmllink(rsquoLogoutrsquoarray(rsquositelogoutrsquo)) gtltligt

ltulgt

Info By default view files for a widget should be placed under the views sub-directory of the directory containing the widget class file The file name must bethe same as the view name

523 Using UserMenu Portlet

It is time for us to make use of our newly completed UserMenu portlet We modify thelayout view file wwwrootblogprotectedviewslayoutscolumn2php as follows

53 Creating Tag Cloud Portlet 43

ltdiv id=sidebargt

ltphp if(Yiiapp()-gtuser-gtisGuest) $this-gtwidget(rsquoUserMenursquo) gt

ltdivgt

In the above we call the widget() method to generate and execute an instance of theUserMenu class Because the portlet should only be displayed to authenticated users weonly call widget() when the isGuest property of the current user is false (meaning theuser is authenticated)

524 Testing UserMenu Portlet

Letrsquos test what we have so far

1 Open a browser window and enter the URL httpwwwexamplecomblogindex

php Verify that there is nothing displayed in the side bar section of the page

2 Click on the Login hyperlink and fill out the login form to login If successful verifythat the UserMenu portlet appears in the side bar and the portlet has the usernameas its title

3 Click on the rsquoLogoutrsquo hyperlink in the UserMenu portlet Verify that the logout actionis successful and the UserMenu portlet disappears

525 Summary

What we have created is a portlet that is highly reusable We can easily reuse it in adifferent project with little or no modification Moreover the design of this portlet followsclosely the philosophy that logic and presentation should be separated While we did notpoint this out in the previous sections such practice is used nearly everywhere in a typicalYii application

53 Creating Tag Cloud Portlet

Tag cloud displays a list of post tags with visual decorations hinting the popularity of eachindividual tag

531 Creating TagCloud Class

We create the TagCloud class in the file wwwrootblogprotectedcomponentsTagCloud

php The file has the following content

44 5 Portlets

Yiiimport(rsquoziiwidgetsCPortletrsquo)

class TagCloud extends CPortlet

public $title=rsquoTagsrsquo

public $maxTags=20

protected function renderContent()

$tags=Tagmodel()-gtfindTagWeights($this-gtmaxTags)

foreach($tags as $tag=gt$weight)

$link=CHtmllink(CHtmlencode($tag) array(rsquopostindexrsquorsquotagrsquo=gt$tag))

echo CHtmltag(rsquospanrsquo array(

rsquoclassrsquo=gtrsquotagrsquo

rsquostylersquo=gtfont-size$weightpt) $link)n

Unlike the UserMenu portlet the TagCloud portlet does not use a view Instead its presen-tation is done in the renderContent() method This is because the presentation does notcontain much HTML tags

We display each tag as a hyperlink to the post index page with the corresponding tagparameter The font size of each tag link is adjusted according to their relative weightamong other tags If a tag has higher frequency value than the other it will have a biggerfont size

532 Using TagCloud Portlet

Usage of the TagCloud portlet is very simple We modify the layout file wwwrootblog

protectedviewslayoutsmainphp as follows

ltdiv id=sidebargt

ltphp if(Yiiapp()-gtuser-gtisGuest) $this-gtwidget(rsquoUserMenursquo) gt

ltphp $this-gtwidget(rsquoTagCloudrsquo array(

rsquomaxTagsrsquo=gtYiiapp()-gtparams[rsquotagCloudCountrsquo]

)) gt

ltdivgt

54 Creating Recent Comments Portlet 45

54 Creating Recent Comments Portlet

In this section we create the last portlet that displays a list of comments recently pub-lished

541 Creating RecentComments Class

We create the RecentComments class in the file wwwrootblogprotectedcomponentsRecentCommentsphp The file has the following content

Yiiimport(rsquoziiwidgetsCPortletrsquo)

class RecentComments extends CPortlet

public $title=rsquoRecent Commentsrsquo

public $maxComments=10

public function getRecentComments()

return Commentmodel()-gtfindRecentComments($this-gtmaxComments)

protected function renderContent()

$this-gtrender(rsquorecentCommentsrsquo)

In the above we invoke the findRecentComments method which is defined in the Comment

class as follows

class Comment extends CActiveRecord

public function findRecentComments($limit=10)

return $this-gtwith(rsquopostrsquo)-gtfindAll(array(

rsquoconditionrsquo=gtrsquotstatus=rsquoselfSTATUS APPROVED

rsquoorderrsquo=gtrsquotcreate time DESCrsquo

rsquolimitrsquo=gt$limit

))

46 5 Portlets

542 Creating recentComments View

The recentComments view is saved in the file wwwrootblogprotectedcomponentsviews

recentCommentsphp It simply displays every comment returned by the RecentComments

getRecentComments() method

543 Using RecentComments Portlet

We modify the layout file wwwrootblogprotectedviewslayoutsmainphp to embedthis last portlet

ltdiv id=sidebargt

ltphp if(Yiiapp()-gtuser-gtisGuest) $this-gtwidget(rsquoUserMenursquo) gt

ltphp $this-gtwidget(rsquoTagCloudrsquo array(

rsquomaxTagsrsquo=gtYiiapp()-gtparams[rsquotagCloudCountrsquo]

)) gt

ltphp $this-gtwidget(rsquoRecentCommentsrsquo array(

rsquomaxCommentsrsquo=gtYiiapp()-gtparams[rsquorecentCommentCountrsquo]

)) gt

ltdivgt

Chapter 6

Final Work

61 Beautifying URLs

The URLs linking various pages of our blog application currently look ugly For examplethe URL for the page showing a post looks like the following

indexphpr=postshowampid=1

In this section we describe how to beautify these URLs and make them SEO-friendlyOur goal is to be able to use the following URLs in the application

1 indexphppostsyii leads to the page showing a list of posts with tag yii

2 indexphppost2A+Test+Post leads to the page showing the detail of the postwith ID 2 whose title is A Test Post

3 indexphppostupdate1 leads to the page that allows updating the post with ID1

Note that in the second URL format we include the post title in the URL This is mainlyto make the URL SEO friendly It is said that search engines may also respect the wordsfound in a URL when it is being indexed

To achieve our goal we modify the application configuration as follows

return array(

rsquocomponentsrsquo=gtarray(

rsquourlManagerrsquo=gtarray(

rsquourlFormatrsquo=gtrsquopathrsquo

rsquorulesrsquo=gtarray(

48 6 Final Work

rsquopostltidd+gtlttitlegtrsquo=gtrsquopostviewrsquorsquopostslttaggtrsquo=gtrsquopostindexrsquo

)

)

)

)

In the above we configure the urlManager component by setting its urlFormat propertyto be path and adding a set of rules

The rules are used by urlManager to parse and create the URLs in the desired formatFor example the first rule says that if a URL indexphppostsyii is requested theurlManager component should be responsible to dispatch the request to the route post

index and generate a tag GET parameter with the value yii On the other hand whencreating a URL with the route postindex and parameter tag the urlManager componentwill also use this rule to generate the desired URL indexphppostsyii For this reasonwe say that urlManager is a two-way URL manager

The urlManager component can further beautify our URLs such as hiding indexphp inthe URLs appending suffix like html to the URLs We can obtain these features easilyby configuring various properties of urlManager in the application configuration For moredetails please refer to the Guide

62 Logging Errors

A production Web application often needs sophisticated logging for various events In ourblog application we would like to log the errors occurring when it is being used Sucherrors could be programming mistakes or usersrsquo misuse of the system Logging these errorswill help us to improve the blog application

We enable the error logging by modifying the application configuration as follows

return array(

rsquopreloadrsquo=gtarray(rsquologrsquo)

rsquocomponentsrsquo=gtarray(

rsquologrsquo=gtarray(

rsquoclassrsquo=gtrsquoCLogRouterrsquo

rsquoroutesrsquo=gtarray(

array(

rsquoclassrsquo=gtrsquoCFileLogRoutersquo

rsquolevelsrsquo=gtrsquoerror warningrsquo

63 Final Tune-up and Deployment 49

)

)

)

)

)

With the above configuration if an error or warning occurs detailed information will belogged and saved in a file located under the directory wwwrootblogprotectedruntime

The log component offers more advanced features such as sending log messages to a listof email addresses displaying log messages in JavaScript console window etc For moredetails please refer to the Guide

63 Final Tune-up and Deployment

We are close to finish our blog application Before deployment we would like to do sometune-ups

631 Changing Home Page

We change to use the post list page as the home page We modify the application config-uration as follows

return array(

rsquodefaultControllerrsquo=gtrsquopostrsquo

)

Tip Because PostController already declares list to be its default action whenwe access the home page of the application we will see the result generated by thelist action of the post controller

632 Enabling Schema Caching

Because ActiveRecord relies on the metadata about tables to determine the column infor-mation it takes time to read the metadata and analyze it This may not be a problemduring development stage but for an application running in production mode it is a totalwaste of time if the database schema does not change Therefore we should enable theschema caching by modifying the application configuration as follows

50 6 Final Work

return array(

rsquocomponentsrsquo=gtarray(

rsquocachersquo=gtarray(

rsquoclassrsquo=gtrsquoCDbCachersquo

)

rsquodbrsquo=gtarray(

rsquoclassrsquo=gtrsquosystemdbCDbConnectionrsquo

rsquoconnectionStringrsquo=gtrsquosqlitewwwrootblogprotecteddatablogdbrsquo

rsquoschemaCachingDurationrsquo=gt3600

)

)

)

In the above we first add a cache component which uses a default SQLite database as thecaching storage If our server is equipped with other caching extensions such as APCwe could change to use them as well We also modify the db component by setting itsschemaCachingDuration property to be 3600 which means the parsed database schemadata can remain valid in cache for 3600 seconds

633 Disabling Debugging Mode

We modify the entry script file wwwrootblogindexphp by removing the line definingthe constant YII DEBUG This constant is useful during development stage because it allowsYii to display more debugging information when an error occurs However when theapplication is running in production mode displaying debugging information is not agood idea because it may contain sensitive information such as where the script file islocated and the content in the file etc

634 Deploying the Application

The final deployment process manly involves copying the directory wwwrootblog to thetarget directory The following checklist shows every needed step

1 Install Yii in the target place if it is not available

2 Copy the entire directory wwwrootblog to the target place

3 Edit the entry script file indexphp by pointing the $yii variable to the new Yiibootstrap file

4 Edit the file protectedyiicphp by setting the $yiic variable to be the new Yiiyiicphp file

64 Future Enhancements 51

5 Change the permission of the directories assets and protectedruntime so that theyare writable by the Web server process

64 Future Enhancements

641 Using a Theme

Without writing any code our blog application is already themeable To use a themewe mainly need to develop the theme by writing customized view files in the theme Forexample to use a theme named classic that uses a different page layout we would createa layout view file wwwrootblogthemesclassicviewslayoutsmainphp We also needto change the application configuration to indicate our choice of the classic theme

return array(

rsquothemersquo=gtrsquoclassicrsquo

)

642 Internationalization

We may also internationalize our blog application so that its pages can be displayed indifferent languages This mainly involves efforts in two aspects

First we may create view files in different languages For example for the list pageof PostController we can create a view file wwwrootblogprotectedviewspostzh cn

listphp When the application is configured to use simplified Chinese (the language codeis zh cn) Yii will automatically use this new view file instead of the original one

Second we may create message translations for those messages generated by code Themessage translations should be saved as files under the directory wwwrootblogprotected

messages We also need to modify the code where we use text strings by enclosing themin the method call Yiit()

For more details about internationalization please refer to the Guide

643 Improving Performance with Cache

While the Yii framework itself is very efficient it is not necessarily true that an applicationwritten in Yii efficient There are several places in our blog application that we can improvethe performance For example the tag clould portlet could be one of the performancebottlenecks because it involves complex database query and PHP logic

52 6 Final Work

We can make use of the sophisticated caching feature provided by Yii to improve theperformance One of the most useful components in Yii is COutputCache which caches afragment of page display so that the underlying code generating the fragment does not needto be executed for every request For example in the layout file wwwrootblogprotected

viewslayoutsmainphp we can enclose the tag cloud portlet with COutputCache

ltphp if($this-gtbeginCache(rsquotagCloudrsquo array(rsquodurationrsquo=gt3600))) gt

ltphp $this-gtwidget(rsquoTagCloudrsquo array(

rsquomaxTagsrsquo=gtYiiapp()-gtparams[rsquotagCloudCountrsquo]

)) gt

ltphp $this-gtendCache() gt

With the above code the tag cloud display will be served from cache instead of beinggenerated on-the-fly for every request The cached content will remain valid in cache for3600 seconds

644 Adding New Features

Our blog application only has very basic functionalities To become a complete blogsystem more features are needed for example calendar portlet email notifications postcategorization archived post portlet and so on We will leave the implementation of thesefeatures to interested readers

  • Contents
  • License
  • Getting Started
    • Building a Blog System using Yii
    • Testdriving with Yii
      • Installing Yii
      • Creating Skeleton Application
      • Application Workflow
        • Requirements Analysis
        • Overall Design
          • Initial Prototyping
            • Setting Up Database
              • Creating Database
              • Establishing Database Connection
                • Scaffolding
                • Authenticating User
                • Summary
                  • Post Management
                    • Customizing Post Model
                      • Customizing rules() Method
                      • Customizing relations() Method
                      • Representing Status in Text
                        • Creating and Updating Posts
                          • Customizing Access Control
                          • Customizing create and update Operations
                            • Displaying Posts
                              • Customizing view Operation
                              • Customizing index Operation
                                • Managing Posts
                                  • Listing Posts in Tabular View
                                  • Deleting Posts
                                      • Comment Management
                                        • Customizing Comment Model
                                          • Customizing rules() Method
                                          • Customizing attributeLabels() Method
                                          • Customizing Saving Process
                                            • Creating and Displaying Comments
                                              • Displaying Comments
                                              • Creating Comments
                                                • Managing Comments
                                                  • Updating and Deleting Comments
                                                  • Approving Comments
                                                      • Portlets
                                                        • Customizing Page Layout
                                                        • Creating User Menu Portlet
                                                          • Creating UserMenu Class
                                                          • Creating userMenu View
                                                          • Using UserMenu Portlet
                                                          • Testing UserMenu Portlet
                                                          • Summary
                                                            • Creating Tag Cloud Portlet
                                                              • Creating TagCloud Class
                                                              • Using TagCloud Portlet
                                                                • Creating Recent Comments Portlet
                                                                  • Creating RecentComments Class
                                                                  • Creating recentComments View
                                                                  • Using RecentComments Portlet
                                                                      • Final Work
                                                                        • Beautifying URLs
                                                                        • Logging Errors
                                                                        • Final Tune-up and Deployment
                                                                          • Changing Home Page
                                                                          • Enabling Schema Caching
                                                                          • Disabling Debugging Mode
                                                                          • Deploying the Application
                                                                            • Future Enhancements
                                                                              • Using a Theme
                                                                              • Internationalization
                                                                              • Improving Performance with Cache
                                                                              • Adding New Features
Page 40: Yii Blog 1.1.0 Demo Tutor

Chapter 4

Comment Management

41 Customizing Comment Model

For the Comment model we mainly need to customize the rules() and attributeLabels()

methods The attributeLabels() method returns a mapping between attribute namesand attribute labels We do not need to touch relations() since the code generated bythe yiic tool is good enough

411 Customizing rules() Method

We first customize the validation rules generated by the yiic tool The following rules areused for comments

public function rules()

return array(

array(rsquocontent author emailrsquo rsquorequiredrsquo)

array(rsquoauthor email urlrsquo rsquolengthrsquo rsquomaxrsquo=gt128)

array(rsquoemailrsquorsquoemailrsquo)

array(rsquourlrsquorsquourlrsquo)

)

In the above we specify that the author email and content attributes are required thelength of author email and url cannot exceed 128 the email attribute must be a validemail address and the url attribute must be a valid URL

412 Customizing attributeLabels() Method

We then customize the attributeLabels() method to declare the label display for eachmodel attribute This method returns an array consisting of name-label pairs When wecall CHtmlactiveLabel() to display an attribute label

34 4 Comment Management

public function attributeLabels()

return array(

rsquoidrsquo =gt rsquoIdrsquo

rsquocontentrsquo =gt rsquoCommentrsquo

rsquostatusrsquo =gt rsquoStatusrsquo

rsquocreate timersquo =gt rsquoCreate Timersquo

rsquoauthorrsquo =gt rsquoNamersquo

rsquoemailrsquo =gt rsquoEmailrsquo

rsquourlrsquo =gt rsquoWebsitersquo

rsquopost idrsquo =gt rsquoPostrsquo

)

Tip If the label for an attribute is not declared in attributeLabels() an algo-rithm will be used to generate an appropriate label For example a label VerifyCode will be generated for attributes verify code or verifyCode

413 Customizing Saving Process

Because we want to record the creation time of a comment we override the beforeSave()

method of Comment like we do for the Post model

protected function beforeSave()

if(parentbeforeSave())

if($this-gtisNewRecord)

$this-gtcreate time=time()

return true

else

return false

42 Creating and Displaying Comments

In this section we implement the comment display and creation features

421 Displaying Comments

Instead of displaying and creating comments on individual pages we use the post detailpage (generated by the view action of PostController) Below the post content display

42 Creating and Displaying Comments 35

we display first a list of comments belonging to that post and then a comment creationform

In order to display comments on the post detail page we modify the view script wwwrootblogprotectedviewspostviewphp as follows

ltdiv id=commentsgt

ltphp if($model-gtcommentCountgt=1) gt

lth3gt

ltphp echo $model-gtcommentCount rsquocomment(s)rsquo gt

lth3gt

ltphp $this-gtrenderPartial(rsquo commentsrsquoarray(

rsquopostrsquo=gt$model

rsquocommentsrsquo=gt$model-gtcomments

)) gt

ltphp endif gt

ltdivgt

In the above we call renderPartial() to render a partial view named comments to displaythe list of comments belonging to the current post Note that in the view we use theexpression $model-gtcomments to retrieve the comments for the post This is valid becausewe have declared a comments relation in the Post class Evaluating this expression wouldtrigger an implicit JOIN database query to bring back the proper comments This featureis known as lazy relational query

The partial view comments is not very interesting It mainly goes through every commentand displays the detail of it Interested readers may refer to wwwrootyiidemosblog

protectedpost commentsphp

422 Creating Comments

To handle comment creation we first modify the actionView() method of PostController

as follows

public function actionView()

$post=$this-gtloadModel()

$comment=$this-gtnewComment($post)

$this-gtrender(rsquoviewrsquoarray(

rsquomodelrsquo=gt$post

rsquocommentrsquo=gt$comment

))

36 4 Comment Management

protected function newComment($post)

$comment=new Comment

if(isset($ POST[rsquoCommentrsquo]))

$comment-gtattributes=$ POST[rsquoCommentrsquo]

if($post-gtaddComment($comment))

if($comment-gtstatus==CommentSTATUS PENDING)

Yiiapp()-gtuser-gtsetFlash(rsquocommentSubmittedrsquorsquoThank yoursquo)

$this-gtrefresh()

return $comment

In the above we call the newComment() method before we render view In the newComment()

method we generate a Comment instance and check if the comment form is submitted If sowe try to add the comment for the post by calling $post-gtaddComment($comment) If it goesthrough we refresh the post detail page In case the comment needs to be approved we willshow a flash message to indicate this decision A flash message is usually a confirmationmessage displayed to end users If the user clicks on the refresh button of his browser themessage will disappear

We also need to modify wwwrootblogprotectedviewspostviewphp furthermore

ltdiv id=commentsgt

lth3gtLeave a Commentlth3gt

ltphp if(Yiiapp()-gtuser-gthasFlash(rsquocommentSubmittedrsquo)) gt

ltdiv class=successgt

ltphp echo Yiiapp()-gtuser-gtgetFlash(rsquocommentSubmittedrsquo) gt

ltdivgt

ltphp else gt

ltphp $this-gtrenderPartial(rsquocomment formrsquoarray(

rsquomodelrsquo=gt$comment

)) gt

ltphp endif gt

ltdivgtlt-- comments --gt

43 Managing Comments 37

In the above code we display the flash message if it is available If not we displaythe comment input form by rendering the partial view wwwrootblogprotectedviews

comment formphp

43 Managing Comments

Comment management includes updating deleting and approving comments These op-erations are implemented as actions in the CommentController class

431 Updating and Deleting Comments

The code generated by yiic for updating and deleting comments remains largely un-changed

432 Approving Comments

When comments are newly created they are in pending approval status and need to beapproved in order to be visible to guest users Approving a comment is mainly aboutchanging the status column of the comment

We create an actionApprove() method in CommentController as follows

public function actionApprove()

if(Yiiapp()-gtrequest-gtisPostRequest)

$comment=$this-gtloadModel()

$comment-gtapprove()

$this-gtredirect(array(rsquoindexrsquo))

else

throw new CHttpException(400rsquoInvalid requestrsquo)

In the above when the approve action is invoked via a POST request we call the approve()

method defined in the Comment model to change the status We then redirect the userbrowser to the page displaying the post that this comment belongs to

We also modify the actionIndex() method of Comment to show all comments We wouldlike to see comments pending approved to show up first

public function actionIndex()

38 4 Comment Management

$dataProvider=new CActiveDataProvider(rsquoCommentrsquo array(

rsquocriteriarsquo=gtarray(

rsquowithrsquo=gtrsquopostrsquo

rsquoorderrsquo=gtrsquotstatus tcreate time DESCrsquo

)

))

$this-gtrender(rsquoindexrsquoarray(

rsquodataProviderrsquo=gt$dataProvider

))

Notice that in the above code because both tbl post and tbl comment have columns statusand create time we need to disambiguate the corresponding column reference by prefixingthem with table alias names As described in the guide the alias for the primary table ina relational query is always t Therefore we are prefixing t to the status and create time

columns in the above code

Like the post index view the index view for CommentController uses [CListView] to displaythe comment list which in turn uses the partial view wwwrootblogprotectedviews

comment viewphp to display the detail of each individual comment We will not go intodetails here Interested readers may refer to the corresponding file in the blog demowwwrootyiidemosblogprotectedviewscomment viewphp

Chapter 5

Portlets

51 Customizing Page Layout

Features like rdquothe most recent commentsrdquo rdquotag cloudrdquo are better to be implemented inportlets A portlet is a pluggable user interface component that renders a fragment ofHTML code In this section we describe how to set up the portlet architecture for ourblog application

Before we go into details about developing portlets we first need to adjust our page layoutso that the portlets can be placed in a side bar

A page layout determines how to share common sections (eg header footer) amongdifferent pages and how to embed contents that are specific for different pages In Yiia page layout is represented as a view under the protectedviewslayouts folder Whenwe call the render() method in a controller the rendering result will be automaticallyembedded into this layout view before it is displayed to end users The default layoutview is named as main which means the wwwrootblogprotectedviewslayoutsmain

php file in our blog application

We could directly modify the main layout to add a side bar However doing so wouldmake some irrelevant pages such as about contact pages to have a side bar which is notalways desirable For this reason we need different layouts for different pages

We thus create two different layouts column1 and column2 The former displays the pagein a single column while the latter displays two columns in the page one of which is theside bar We will use column2 layout for the post and comment pages and column1 forthe rest of the pages Because both column1 and column2 still share the same header andfooter sections we let them to share the main layout

Based on the above description we create the column1 layout as follows

ltphp $this-gtbeginContent(rsquolayoutsmainrsquo) gt

ltdiv class=containergt

40 5 Portlets

ltdiv id=contentgt

ltphp echo $content gt

ltdivgtlt-- content --gt

ltdivgt

ltphp $this-gtendContent() gt

and the column2 layout as follows

ltphp $this-gtbeginContent(rsquolayoutsmainrsquo) gt

ltdiv class=containergt

ltdiv class=span-18gt

ltdiv id=contentgt

ltphp echo $content gt

ltdivgtlt-- content --gt

ltdivgt

ltdiv class=span-6 lastgt

ltdiv id=sidebargt

portlets to be placed here

ltdivgtlt-- sidebar --gt

ltdivgt

ltdivgt

ltphp $this-gtendContent() gt

As we can see both column1 and column2 are enclosed in a pair of beginContent andendContent calls They essentially use the specified view layoutsmain to decorate theenclosed content In all these layout views the variable $content refers to the dynamiccontent to be embedded into the layout

Besides customizing the layout view file we also need to adjust the CSS file wwwroot

blogcssmaincss so that the overall appearance would look like what we see in the blogdemo We will not go into details here

Info The skeleton application generated by the yiic tool uses the BluePrint CSSframework It provides a nice font reset and a robust grid system For more detailson how to use this framework please refer to its documentation

After creating the new layouts we need to set the layout property of controllers to makeuse of them So far we have three controllers SiteController PostController andCommentController We set SiteController to use column1 as the default layout whilethe rest two of the controllers use column2 for example

class PostController extends Controller

52 Creating User Menu Portlet 41

public $layout=rsquocolumn2rsquo

Tip If some view of the controller does not use the same layout as declaredby the layout property it may customize it in the view by a simple assignment$this-gtlayout=rsquonewlayoutrsquo

52 Creating User Menu Portlet

Based on the requirements analysis we need three different portlets the rdquouser menurdquoportlet the rdquotag cloudrdquo portlet and the rdquorecent commentsrdquo portlet We will develop theseportlets by extending the [CPortlet] widget provided by Yii

In this section we will develop our first concrete portlet - the user menu portlet whichdisplays a list of menu items that are only available to authenticated users The menucontains four items

bull Approve Comments a hyperlink that leads to a list of comments pending approval

bull Create New Post a hyperlink that leads to the post creation page

bull Manage Posts a hyperlink that leads to the post management page

bull Logout a link button that would log out the current user

521 Creating UserMenu Class

We create the UserMenu class to represent the logic part of the user menu portlet Theclass is saved in the file wwwrootblogprotectedcomponentsUserMenuphp which has thefollowing content

Yiiimport(rsquoziiwidgetsCPortletrsquo)

class UserMenu extends CPortlet

public function init()

$this-gttitle=CHtmlencode(Yiiapp()-gtuser-gtname)

parentinit()

42 5 Portlets

protected function renderContent()

$this-gtrender(rsquouserMenursquo)

The UserMenu class extends from the CPortlet class that we created previously It overridesboth the init() method and the renderContent() method of CPortlet The former setsthe portlet title to be the name of the current user the latter generates the portlet bodycontent by rendering a view named userMenu

Tip Notice that we have to explicitly include the CPortlet class by calling Yii

import() before we refer to it the first time This is because CPortlet is part of thezii project ndash the official extension library for Yii For performance considerationclasses in this project are not listed as core classes Therefore we have to import itbefore we use it the first time

522 Creating userMenu View

Next we create the userMenu view which is saved in the file wwwrootblogprotected

componentsviewsuserMenuphp

ltulgt

ltligtltphp echo CHtmllink(rsquoCreate New Postrsquoarray(rsquopostcreatersquo)) gtltligt

ltligtltphp echo CHtmllink(rsquoManage Postsrsquoarray(rsquopostadminrsquo)) gtltligt

ltligtltphp echo CHtmllink(rsquoApprove Commentsrsquoarray(rsquocommentindexrsquo))

rsquo (rsquo Commentmodel()-gtpendingCommentCount rsquo)rsquo gtltligt

ltligtltphp echo CHtmllink(rsquoLogoutrsquoarray(rsquositelogoutrsquo)) gtltligt

ltulgt

Info By default view files for a widget should be placed under the views sub-directory of the directory containing the widget class file The file name must bethe same as the view name

523 Using UserMenu Portlet

It is time for us to make use of our newly completed UserMenu portlet We modify thelayout view file wwwrootblogprotectedviewslayoutscolumn2php as follows

53 Creating Tag Cloud Portlet 43

ltdiv id=sidebargt

ltphp if(Yiiapp()-gtuser-gtisGuest) $this-gtwidget(rsquoUserMenursquo) gt

ltdivgt

In the above we call the widget() method to generate and execute an instance of theUserMenu class Because the portlet should only be displayed to authenticated users weonly call widget() when the isGuest property of the current user is false (meaning theuser is authenticated)

524 Testing UserMenu Portlet

Letrsquos test what we have so far

1 Open a browser window and enter the URL httpwwwexamplecomblogindex

php Verify that there is nothing displayed in the side bar section of the page

2 Click on the Login hyperlink and fill out the login form to login If successful verifythat the UserMenu portlet appears in the side bar and the portlet has the usernameas its title

3 Click on the rsquoLogoutrsquo hyperlink in the UserMenu portlet Verify that the logout actionis successful and the UserMenu portlet disappears

525 Summary

What we have created is a portlet that is highly reusable We can easily reuse it in adifferent project with little or no modification Moreover the design of this portlet followsclosely the philosophy that logic and presentation should be separated While we did notpoint this out in the previous sections such practice is used nearly everywhere in a typicalYii application

53 Creating Tag Cloud Portlet

Tag cloud displays a list of post tags with visual decorations hinting the popularity of eachindividual tag

531 Creating TagCloud Class

We create the TagCloud class in the file wwwrootblogprotectedcomponentsTagCloud

php The file has the following content

44 5 Portlets

Yiiimport(rsquoziiwidgetsCPortletrsquo)

class TagCloud extends CPortlet

public $title=rsquoTagsrsquo

public $maxTags=20

protected function renderContent()

$tags=Tagmodel()-gtfindTagWeights($this-gtmaxTags)

foreach($tags as $tag=gt$weight)

$link=CHtmllink(CHtmlencode($tag) array(rsquopostindexrsquorsquotagrsquo=gt$tag))

echo CHtmltag(rsquospanrsquo array(

rsquoclassrsquo=gtrsquotagrsquo

rsquostylersquo=gtfont-size$weightpt) $link)n

Unlike the UserMenu portlet the TagCloud portlet does not use a view Instead its presen-tation is done in the renderContent() method This is because the presentation does notcontain much HTML tags

We display each tag as a hyperlink to the post index page with the corresponding tagparameter The font size of each tag link is adjusted according to their relative weightamong other tags If a tag has higher frequency value than the other it will have a biggerfont size

532 Using TagCloud Portlet

Usage of the TagCloud portlet is very simple We modify the layout file wwwrootblog

protectedviewslayoutsmainphp as follows

ltdiv id=sidebargt

ltphp if(Yiiapp()-gtuser-gtisGuest) $this-gtwidget(rsquoUserMenursquo) gt

ltphp $this-gtwidget(rsquoTagCloudrsquo array(

rsquomaxTagsrsquo=gtYiiapp()-gtparams[rsquotagCloudCountrsquo]

)) gt

ltdivgt

54 Creating Recent Comments Portlet 45

54 Creating Recent Comments Portlet

In this section we create the last portlet that displays a list of comments recently pub-lished

541 Creating RecentComments Class

We create the RecentComments class in the file wwwrootblogprotectedcomponentsRecentCommentsphp The file has the following content

Yiiimport(rsquoziiwidgetsCPortletrsquo)

class RecentComments extends CPortlet

public $title=rsquoRecent Commentsrsquo

public $maxComments=10

public function getRecentComments()

return Commentmodel()-gtfindRecentComments($this-gtmaxComments)

protected function renderContent()

$this-gtrender(rsquorecentCommentsrsquo)

In the above we invoke the findRecentComments method which is defined in the Comment

class as follows

class Comment extends CActiveRecord

public function findRecentComments($limit=10)

return $this-gtwith(rsquopostrsquo)-gtfindAll(array(

rsquoconditionrsquo=gtrsquotstatus=rsquoselfSTATUS APPROVED

rsquoorderrsquo=gtrsquotcreate time DESCrsquo

rsquolimitrsquo=gt$limit

))

46 5 Portlets

542 Creating recentComments View

The recentComments view is saved in the file wwwrootblogprotectedcomponentsviews

recentCommentsphp It simply displays every comment returned by the RecentComments

getRecentComments() method

543 Using RecentComments Portlet

We modify the layout file wwwrootblogprotectedviewslayoutsmainphp to embedthis last portlet

ltdiv id=sidebargt

ltphp if(Yiiapp()-gtuser-gtisGuest) $this-gtwidget(rsquoUserMenursquo) gt

ltphp $this-gtwidget(rsquoTagCloudrsquo array(

rsquomaxTagsrsquo=gtYiiapp()-gtparams[rsquotagCloudCountrsquo]

)) gt

ltphp $this-gtwidget(rsquoRecentCommentsrsquo array(

rsquomaxCommentsrsquo=gtYiiapp()-gtparams[rsquorecentCommentCountrsquo]

)) gt

ltdivgt

Chapter 6

Final Work

61 Beautifying URLs

The URLs linking various pages of our blog application currently look ugly For examplethe URL for the page showing a post looks like the following

indexphpr=postshowampid=1

In this section we describe how to beautify these URLs and make them SEO-friendlyOur goal is to be able to use the following URLs in the application

1 indexphppostsyii leads to the page showing a list of posts with tag yii

2 indexphppost2A+Test+Post leads to the page showing the detail of the postwith ID 2 whose title is A Test Post

3 indexphppostupdate1 leads to the page that allows updating the post with ID1

Note that in the second URL format we include the post title in the URL This is mainlyto make the URL SEO friendly It is said that search engines may also respect the wordsfound in a URL when it is being indexed

To achieve our goal we modify the application configuration as follows

return array(

rsquocomponentsrsquo=gtarray(

rsquourlManagerrsquo=gtarray(

rsquourlFormatrsquo=gtrsquopathrsquo

rsquorulesrsquo=gtarray(

48 6 Final Work

rsquopostltidd+gtlttitlegtrsquo=gtrsquopostviewrsquorsquopostslttaggtrsquo=gtrsquopostindexrsquo

)

)

)

)

In the above we configure the urlManager component by setting its urlFormat propertyto be path and adding a set of rules

The rules are used by urlManager to parse and create the URLs in the desired formatFor example the first rule says that if a URL indexphppostsyii is requested theurlManager component should be responsible to dispatch the request to the route post

index and generate a tag GET parameter with the value yii On the other hand whencreating a URL with the route postindex and parameter tag the urlManager componentwill also use this rule to generate the desired URL indexphppostsyii For this reasonwe say that urlManager is a two-way URL manager

The urlManager component can further beautify our URLs such as hiding indexphp inthe URLs appending suffix like html to the URLs We can obtain these features easilyby configuring various properties of urlManager in the application configuration For moredetails please refer to the Guide

62 Logging Errors

A production Web application often needs sophisticated logging for various events In ourblog application we would like to log the errors occurring when it is being used Sucherrors could be programming mistakes or usersrsquo misuse of the system Logging these errorswill help us to improve the blog application

We enable the error logging by modifying the application configuration as follows

return array(

rsquopreloadrsquo=gtarray(rsquologrsquo)

rsquocomponentsrsquo=gtarray(

rsquologrsquo=gtarray(

rsquoclassrsquo=gtrsquoCLogRouterrsquo

rsquoroutesrsquo=gtarray(

array(

rsquoclassrsquo=gtrsquoCFileLogRoutersquo

rsquolevelsrsquo=gtrsquoerror warningrsquo

63 Final Tune-up and Deployment 49

)

)

)

)

)

With the above configuration if an error or warning occurs detailed information will belogged and saved in a file located under the directory wwwrootblogprotectedruntime

The log component offers more advanced features such as sending log messages to a listof email addresses displaying log messages in JavaScript console window etc For moredetails please refer to the Guide

63 Final Tune-up and Deployment

We are close to finish our blog application Before deployment we would like to do sometune-ups

631 Changing Home Page

We change to use the post list page as the home page We modify the application config-uration as follows

return array(

rsquodefaultControllerrsquo=gtrsquopostrsquo

)

Tip Because PostController already declares list to be its default action whenwe access the home page of the application we will see the result generated by thelist action of the post controller

632 Enabling Schema Caching

Because ActiveRecord relies on the metadata about tables to determine the column infor-mation it takes time to read the metadata and analyze it This may not be a problemduring development stage but for an application running in production mode it is a totalwaste of time if the database schema does not change Therefore we should enable theschema caching by modifying the application configuration as follows

50 6 Final Work

return array(

rsquocomponentsrsquo=gtarray(

rsquocachersquo=gtarray(

rsquoclassrsquo=gtrsquoCDbCachersquo

)

rsquodbrsquo=gtarray(

rsquoclassrsquo=gtrsquosystemdbCDbConnectionrsquo

rsquoconnectionStringrsquo=gtrsquosqlitewwwrootblogprotecteddatablogdbrsquo

rsquoschemaCachingDurationrsquo=gt3600

)

)

)

In the above we first add a cache component which uses a default SQLite database as thecaching storage If our server is equipped with other caching extensions such as APCwe could change to use them as well We also modify the db component by setting itsschemaCachingDuration property to be 3600 which means the parsed database schemadata can remain valid in cache for 3600 seconds

633 Disabling Debugging Mode

We modify the entry script file wwwrootblogindexphp by removing the line definingthe constant YII DEBUG This constant is useful during development stage because it allowsYii to display more debugging information when an error occurs However when theapplication is running in production mode displaying debugging information is not agood idea because it may contain sensitive information such as where the script file islocated and the content in the file etc

634 Deploying the Application

The final deployment process manly involves copying the directory wwwrootblog to thetarget directory The following checklist shows every needed step

1 Install Yii in the target place if it is not available

2 Copy the entire directory wwwrootblog to the target place

3 Edit the entry script file indexphp by pointing the $yii variable to the new Yiibootstrap file

4 Edit the file protectedyiicphp by setting the $yiic variable to be the new Yiiyiicphp file

64 Future Enhancements 51

5 Change the permission of the directories assets and protectedruntime so that theyare writable by the Web server process

64 Future Enhancements

641 Using a Theme

Without writing any code our blog application is already themeable To use a themewe mainly need to develop the theme by writing customized view files in the theme Forexample to use a theme named classic that uses a different page layout we would createa layout view file wwwrootblogthemesclassicviewslayoutsmainphp We also needto change the application configuration to indicate our choice of the classic theme

return array(

rsquothemersquo=gtrsquoclassicrsquo

)

642 Internationalization

We may also internationalize our blog application so that its pages can be displayed indifferent languages This mainly involves efforts in two aspects

First we may create view files in different languages For example for the list pageof PostController we can create a view file wwwrootblogprotectedviewspostzh cn

listphp When the application is configured to use simplified Chinese (the language codeis zh cn) Yii will automatically use this new view file instead of the original one

Second we may create message translations for those messages generated by code Themessage translations should be saved as files under the directory wwwrootblogprotected

messages We also need to modify the code where we use text strings by enclosing themin the method call Yiit()

For more details about internationalization please refer to the Guide

643 Improving Performance with Cache

While the Yii framework itself is very efficient it is not necessarily true that an applicationwritten in Yii efficient There are several places in our blog application that we can improvethe performance For example the tag clould portlet could be one of the performancebottlenecks because it involves complex database query and PHP logic

52 6 Final Work

We can make use of the sophisticated caching feature provided by Yii to improve theperformance One of the most useful components in Yii is COutputCache which caches afragment of page display so that the underlying code generating the fragment does not needto be executed for every request For example in the layout file wwwrootblogprotected

viewslayoutsmainphp we can enclose the tag cloud portlet with COutputCache

ltphp if($this-gtbeginCache(rsquotagCloudrsquo array(rsquodurationrsquo=gt3600))) gt

ltphp $this-gtwidget(rsquoTagCloudrsquo array(

rsquomaxTagsrsquo=gtYiiapp()-gtparams[rsquotagCloudCountrsquo]

)) gt

ltphp $this-gtendCache() gt

With the above code the tag cloud display will be served from cache instead of beinggenerated on-the-fly for every request The cached content will remain valid in cache for3600 seconds

644 Adding New Features

Our blog application only has very basic functionalities To become a complete blogsystem more features are needed for example calendar portlet email notifications postcategorization archived post portlet and so on We will leave the implementation of thesefeatures to interested readers

  • Contents
  • License
  • Getting Started
    • Building a Blog System using Yii
    • Testdriving with Yii
      • Installing Yii
      • Creating Skeleton Application
      • Application Workflow
        • Requirements Analysis
        • Overall Design
          • Initial Prototyping
            • Setting Up Database
              • Creating Database
              • Establishing Database Connection
                • Scaffolding
                • Authenticating User
                • Summary
                  • Post Management
                    • Customizing Post Model
                      • Customizing rules() Method
                      • Customizing relations() Method
                      • Representing Status in Text
                        • Creating and Updating Posts
                          • Customizing Access Control
                          • Customizing create and update Operations
                            • Displaying Posts
                              • Customizing view Operation
                              • Customizing index Operation
                                • Managing Posts
                                  • Listing Posts in Tabular View
                                  • Deleting Posts
                                      • Comment Management
                                        • Customizing Comment Model
                                          • Customizing rules() Method
                                          • Customizing attributeLabels() Method
                                          • Customizing Saving Process
                                            • Creating and Displaying Comments
                                              • Displaying Comments
                                              • Creating Comments
                                                • Managing Comments
                                                  • Updating and Deleting Comments
                                                  • Approving Comments
                                                      • Portlets
                                                        • Customizing Page Layout
                                                        • Creating User Menu Portlet
                                                          • Creating UserMenu Class
                                                          • Creating userMenu View
                                                          • Using UserMenu Portlet
                                                          • Testing UserMenu Portlet
                                                          • Summary
                                                            • Creating Tag Cloud Portlet
                                                              • Creating TagCloud Class
                                                              • Using TagCloud Portlet
                                                                • Creating Recent Comments Portlet
                                                                  • Creating RecentComments Class
                                                                  • Creating recentComments View
                                                                  • Using RecentComments Portlet
                                                                      • Final Work
                                                                        • Beautifying URLs
                                                                        • Logging Errors
                                                                        • Final Tune-up and Deployment
                                                                          • Changing Home Page
                                                                          • Enabling Schema Caching
                                                                          • Disabling Debugging Mode
                                                                          • Deploying the Application
                                                                            • Future Enhancements
                                                                              • Using a Theme
                                                                              • Internationalization
                                                                              • Improving Performance with Cache
                                                                              • Adding New Features
Page 41: Yii Blog 1.1.0 Demo Tutor

34 4 Comment Management

public function attributeLabels()

return array(

rsquoidrsquo =gt rsquoIdrsquo

rsquocontentrsquo =gt rsquoCommentrsquo

rsquostatusrsquo =gt rsquoStatusrsquo

rsquocreate timersquo =gt rsquoCreate Timersquo

rsquoauthorrsquo =gt rsquoNamersquo

rsquoemailrsquo =gt rsquoEmailrsquo

rsquourlrsquo =gt rsquoWebsitersquo

rsquopost idrsquo =gt rsquoPostrsquo

)

Tip If the label for an attribute is not declared in attributeLabels() an algo-rithm will be used to generate an appropriate label For example a label VerifyCode will be generated for attributes verify code or verifyCode

413 Customizing Saving Process

Because we want to record the creation time of a comment we override the beforeSave()

method of Comment like we do for the Post model

protected function beforeSave()

if(parentbeforeSave())

if($this-gtisNewRecord)

$this-gtcreate time=time()

return true

else

return false

42 Creating and Displaying Comments

In this section we implement the comment display and creation features

421 Displaying Comments

Instead of displaying and creating comments on individual pages we use the post detailpage (generated by the view action of PostController) Below the post content display

42 Creating and Displaying Comments 35

we display first a list of comments belonging to that post and then a comment creationform

In order to display comments on the post detail page we modify the view script wwwrootblogprotectedviewspostviewphp as follows

ltdiv id=commentsgt

ltphp if($model-gtcommentCountgt=1) gt

lth3gt

ltphp echo $model-gtcommentCount rsquocomment(s)rsquo gt

lth3gt

ltphp $this-gtrenderPartial(rsquo commentsrsquoarray(

rsquopostrsquo=gt$model

rsquocommentsrsquo=gt$model-gtcomments

)) gt

ltphp endif gt

ltdivgt

In the above we call renderPartial() to render a partial view named comments to displaythe list of comments belonging to the current post Note that in the view we use theexpression $model-gtcomments to retrieve the comments for the post This is valid becausewe have declared a comments relation in the Post class Evaluating this expression wouldtrigger an implicit JOIN database query to bring back the proper comments This featureis known as lazy relational query

The partial view comments is not very interesting It mainly goes through every commentand displays the detail of it Interested readers may refer to wwwrootyiidemosblog

protectedpost commentsphp

422 Creating Comments

To handle comment creation we first modify the actionView() method of PostController

as follows

public function actionView()

$post=$this-gtloadModel()

$comment=$this-gtnewComment($post)

$this-gtrender(rsquoviewrsquoarray(

rsquomodelrsquo=gt$post

rsquocommentrsquo=gt$comment

))

36 4 Comment Management

protected function newComment($post)

$comment=new Comment

if(isset($ POST[rsquoCommentrsquo]))

$comment-gtattributes=$ POST[rsquoCommentrsquo]

if($post-gtaddComment($comment))

if($comment-gtstatus==CommentSTATUS PENDING)

Yiiapp()-gtuser-gtsetFlash(rsquocommentSubmittedrsquorsquoThank yoursquo)

$this-gtrefresh()

return $comment

In the above we call the newComment() method before we render view In the newComment()

method we generate a Comment instance and check if the comment form is submitted If sowe try to add the comment for the post by calling $post-gtaddComment($comment) If it goesthrough we refresh the post detail page In case the comment needs to be approved we willshow a flash message to indicate this decision A flash message is usually a confirmationmessage displayed to end users If the user clicks on the refresh button of his browser themessage will disappear

We also need to modify wwwrootblogprotectedviewspostviewphp furthermore

ltdiv id=commentsgt

lth3gtLeave a Commentlth3gt

ltphp if(Yiiapp()-gtuser-gthasFlash(rsquocommentSubmittedrsquo)) gt

ltdiv class=successgt

ltphp echo Yiiapp()-gtuser-gtgetFlash(rsquocommentSubmittedrsquo) gt

ltdivgt

ltphp else gt

ltphp $this-gtrenderPartial(rsquocomment formrsquoarray(

rsquomodelrsquo=gt$comment

)) gt

ltphp endif gt

ltdivgtlt-- comments --gt

43 Managing Comments 37

In the above code we display the flash message if it is available If not we displaythe comment input form by rendering the partial view wwwrootblogprotectedviews

comment formphp

43 Managing Comments

Comment management includes updating deleting and approving comments These op-erations are implemented as actions in the CommentController class

431 Updating and Deleting Comments

The code generated by yiic for updating and deleting comments remains largely un-changed

432 Approving Comments

When comments are newly created they are in pending approval status and need to beapproved in order to be visible to guest users Approving a comment is mainly aboutchanging the status column of the comment

We create an actionApprove() method in CommentController as follows

public function actionApprove()

if(Yiiapp()-gtrequest-gtisPostRequest)

$comment=$this-gtloadModel()

$comment-gtapprove()

$this-gtredirect(array(rsquoindexrsquo))

else

throw new CHttpException(400rsquoInvalid requestrsquo)

In the above when the approve action is invoked via a POST request we call the approve()

method defined in the Comment model to change the status We then redirect the userbrowser to the page displaying the post that this comment belongs to

We also modify the actionIndex() method of Comment to show all comments We wouldlike to see comments pending approved to show up first

public function actionIndex()

38 4 Comment Management

$dataProvider=new CActiveDataProvider(rsquoCommentrsquo array(

rsquocriteriarsquo=gtarray(

rsquowithrsquo=gtrsquopostrsquo

rsquoorderrsquo=gtrsquotstatus tcreate time DESCrsquo

)

))

$this-gtrender(rsquoindexrsquoarray(

rsquodataProviderrsquo=gt$dataProvider

))

Notice that in the above code because both tbl post and tbl comment have columns statusand create time we need to disambiguate the corresponding column reference by prefixingthem with table alias names As described in the guide the alias for the primary table ina relational query is always t Therefore we are prefixing t to the status and create time

columns in the above code

Like the post index view the index view for CommentController uses [CListView] to displaythe comment list which in turn uses the partial view wwwrootblogprotectedviews

comment viewphp to display the detail of each individual comment We will not go intodetails here Interested readers may refer to the corresponding file in the blog demowwwrootyiidemosblogprotectedviewscomment viewphp

Chapter 5

Portlets

51 Customizing Page Layout

Features like rdquothe most recent commentsrdquo rdquotag cloudrdquo are better to be implemented inportlets A portlet is a pluggable user interface component that renders a fragment ofHTML code In this section we describe how to set up the portlet architecture for ourblog application

Before we go into details about developing portlets we first need to adjust our page layoutso that the portlets can be placed in a side bar

A page layout determines how to share common sections (eg header footer) amongdifferent pages and how to embed contents that are specific for different pages In Yiia page layout is represented as a view under the protectedviewslayouts folder Whenwe call the render() method in a controller the rendering result will be automaticallyembedded into this layout view before it is displayed to end users The default layoutview is named as main which means the wwwrootblogprotectedviewslayoutsmain

php file in our blog application

We could directly modify the main layout to add a side bar However doing so wouldmake some irrelevant pages such as about contact pages to have a side bar which is notalways desirable For this reason we need different layouts for different pages

We thus create two different layouts column1 and column2 The former displays the pagein a single column while the latter displays two columns in the page one of which is theside bar We will use column2 layout for the post and comment pages and column1 forthe rest of the pages Because both column1 and column2 still share the same header andfooter sections we let them to share the main layout

Based on the above description we create the column1 layout as follows

ltphp $this-gtbeginContent(rsquolayoutsmainrsquo) gt

ltdiv class=containergt

40 5 Portlets

ltdiv id=contentgt

ltphp echo $content gt

ltdivgtlt-- content --gt

ltdivgt

ltphp $this-gtendContent() gt

and the column2 layout as follows

ltphp $this-gtbeginContent(rsquolayoutsmainrsquo) gt

ltdiv class=containergt

ltdiv class=span-18gt

ltdiv id=contentgt

ltphp echo $content gt

ltdivgtlt-- content --gt

ltdivgt

ltdiv class=span-6 lastgt

ltdiv id=sidebargt

portlets to be placed here

ltdivgtlt-- sidebar --gt

ltdivgt

ltdivgt

ltphp $this-gtendContent() gt

As we can see both column1 and column2 are enclosed in a pair of beginContent andendContent calls They essentially use the specified view layoutsmain to decorate theenclosed content In all these layout views the variable $content refers to the dynamiccontent to be embedded into the layout

Besides customizing the layout view file we also need to adjust the CSS file wwwroot

blogcssmaincss so that the overall appearance would look like what we see in the blogdemo We will not go into details here

Info The skeleton application generated by the yiic tool uses the BluePrint CSSframework It provides a nice font reset and a robust grid system For more detailson how to use this framework please refer to its documentation

After creating the new layouts we need to set the layout property of controllers to makeuse of them So far we have three controllers SiteController PostController andCommentController We set SiteController to use column1 as the default layout whilethe rest two of the controllers use column2 for example

class PostController extends Controller

52 Creating User Menu Portlet 41

public $layout=rsquocolumn2rsquo

Tip If some view of the controller does not use the same layout as declaredby the layout property it may customize it in the view by a simple assignment$this-gtlayout=rsquonewlayoutrsquo

52 Creating User Menu Portlet

Based on the requirements analysis we need three different portlets the rdquouser menurdquoportlet the rdquotag cloudrdquo portlet and the rdquorecent commentsrdquo portlet We will develop theseportlets by extending the [CPortlet] widget provided by Yii

In this section we will develop our first concrete portlet - the user menu portlet whichdisplays a list of menu items that are only available to authenticated users The menucontains four items

bull Approve Comments a hyperlink that leads to a list of comments pending approval

bull Create New Post a hyperlink that leads to the post creation page

bull Manage Posts a hyperlink that leads to the post management page

bull Logout a link button that would log out the current user

521 Creating UserMenu Class

We create the UserMenu class to represent the logic part of the user menu portlet Theclass is saved in the file wwwrootblogprotectedcomponentsUserMenuphp which has thefollowing content

Yiiimport(rsquoziiwidgetsCPortletrsquo)

class UserMenu extends CPortlet

public function init()

$this-gttitle=CHtmlencode(Yiiapp()-gtuser-gtname)

parentinit()

42 5 Portlets

protected function renderContent()

$this-gtrender(rsquouserMenursquo)

The UserMenu class extends from the CPortlet class that we created previously It overridesboth the init() method and the renderContent() method of CPortlet The former setsthe portlet title to be the name of the current user the latter generates the portlet bodycontent by rendering a view named userMenu

Tip Notice that we have to explicitly include the CPortlet class by calling Yii

import() before we refer to it the first time This is because CPortlet is part of thezii project ndash the official extension library for Yii For performance considerationclasses in this project are not listed as core classes Therefore we have to import itbefore we use it the first time

522 Creating userMenu View

Next we create the userMenu view which is saved in the file wwwrootblogprotected

componentsviewsuserMenuphp

ltulgt

ltligtltphp echo CHtmllink(rsquoCreate New Postrsquoarray(rsquopostcreatersquo)) gtltligt

ltligtltphp echo CHtmllink(rsquoManage Postsrsquoarray(rsquopostadminrsquo)) gtltligt

ltligtltphp echo CHtmllink(rsquoApprove Commentsrsquoarray(rsquocommentindexrsquo))

rsquo (rsquo Commentmodel()-gtpendingCommentCount rsquo)rsquo gtltligt

ltligtltphp echo CHtmllink(rsquoLogoutrsquoarray(rsquositelogoutrsquo)) gtltligt

ltulgt

Info By default view files for a widget should be placed under the views sub-directory of the directory containing the widget class file The file name must bethe same as the view name

523 Using UserMenu Portlet

It is time for us to make use of our newly completed UserMenu portlet We modify thelayout view file wwwrootblogprotectedviewslayoutscolumn2php as follows

53 Creating Tag Cloud Portlet 43

ltdiv id=sidebargt

ltphp if(Yiiapp()-gtuser-gtisGuest) $this-gtwidget(rsquoUserMenursquo) gt

ltdivgt

In the above we call the widget() method to generate and execute an instance of theUserMenu class Because the portlet should only be displayed to authenticated users weonly call widget() when the isGuest property of the current user is false (meaning theuser is authenticated)

524 Testing UserMenu Portlet

Letrsquos test what we have so far

1 Open a browser window and enter the URL httpwwwexamplecomblogindex

php Verify that there is nothing displayed in the side bar section of the page

2 Click on the Login hyperlink and fill out the login form to login If successful verifythat the UserMenu portlet appears in the side bar and the portlet has the usernameas its title

3 Click on the rsquoLogoutrsquo hyperlink in the UserMenu portlet Verify that the logout actionis successful and the UserMenu portlet disappears

525 Summary

What we have created is a portlet that is highly reusable We can easily reuse it in adifferent project with little or no modification Moreover the design of this portlet followsclosely the philosophy that logic and presentation should be separated While we did notpoint this out in the previous sections such practice is used nearly everywhere in a typicalYii application

53 Creating Tag Cloud Portlet

Tag cloud displays a list of post tags with visual decorations hinting the popularity of eachindividual tag

531 Creating TagCloud Class

We create the TagCloud class in the file wwwrootblogprotectedcomponentsTagCloud

php The file has the following content

44 5 Portlets

Yiiimport(rsquoziiwidgetsCPortletrsquo)

class TagCloud extends CPortlet

public $title=rsquoTagsrsquo

public $maxTags=20

protected function renderContent()

$tags=Tagmodel()-gtfindTagWeights($this-gtmaxTags)

foreach($tags as $tag=gt$weight)

$link=CHtmllink(CHtmlencode($tag) array(rsquopostindexrsquorsquotagrsquo=gt$tag))

echo CHtmltag(rsquospanrsquo array(

rsquoclassrsquo=gtrsquotagrsquo

rsquostylersquo=gtfont-size$weightpt) $link)n

Unlike the UserMenu portlet the TagCloud portlet does not use a view Instead its presen-tation is done in the renderContent() method This is because the presentation does notcontain much HTML tags

We display each tag as a hyperlink to the post index page with the corresponding tagparameter The font size of each tag link is adjusted according to their relative weightamong other tags If a tag has higher frequency value than the other it will have a biggerfont size

532 Using TagCloud Portlet

Usage of the TagCloud portlet is very simple We modify the layout file wwwrootblog

protectedviewslayoutsmainphp as follows

ltdiv id=sidebargt

ltphp if(Yiiapp()-gtuser-gtisGuest) $this-gtwidget(rsquoUserMenursquo) gt

ltphp $this-gtwidget(rsquoTagCloudrsquo array(

rsquomaxTagsrsquo=gtYiiapp()-gtparams[rsquotagCloudCountrsquo]

)) gt

ltdivgt

54 Creating Recent Comments Portlet 45

54 Creating Recent Comments Portlet

In this section we create the last portlet that displays a list of comments recently pub-lished

541 Creating RecentComments Class

We create the RecentComments class in the file wwwrootblogprotectedcomponentsRecentCommentsphp The file has the following content

Yiiimport(rsquoziiwidgetsCPortletrsquo)

class RecentComments extends CPortlet

public $title=rsquoRecent Commentsrsquo

public $maxComments=10

public function getRecentComments()

return Commentmodel()-gtfindRecentComments($this-gtmaxComments)

protected function renderContent()

$this-gtrender(rsquorecentCommentsrsquo)

In the above we invoke the findRecentComments method which is defined in the Comment

class as follows

class Comment extends CActiveRecord

public function findRecentComments($limit=10)

return $this-gtwith(rsquopostrsquo)-gtfindAll(array(

rsquoconditionrsquo=gtrsquotstatus=rsquoselfSTATUS APPROVED

rsquoorderrsquo=gtrsquotcreate time DESCrsquo

rsquolimitrsquo=gt$limit

))

46 5 Portlets

542 Creating recentComments View

The recentComments view is saved in the file wwwrootblogprotectedcomponentsviews

recentCommentsphp It simply displays every comment returned by the RecentComments

getRecentComments() method

543 Using RecentComments Portlet

We modify the layout file wwwrootblogprotectedviewslayoutsmainphp to embedthis last portlet

ltdiv id=sidebargt

ltphp if(Yiiapp()-gtuser-gtisGuest) $this-gtwidget(rsquoUserMenursquo) gt

ltphp $this-gtwidget(rsquoTagCloudrsquo array(

rsquomaxTagsrsquo=gtYiiapp()-gtparams[rsquotagCloudCountrsquo]

)) gt

ltphp $this-gtwidget(rsquoRecentCommentsrsquo array(

rsquomaxCommentsrsquo=gtYiiapp()-gtparams[rsquorecentCommentCountrsquo]

)) gt

ltdivgt

Chapter 6

Final Work

61 Beautifying URLs

The URLs linking various pages of our blog application currently look ugly For examplethe URL for the page showing a post looks like the following

indexphpr=postshowampid=1

In this section we describe how to beautify these URLs and make them SEO-friendlyOur goal is to be able to use the following URLs in the application

1 indexphppostsyii leads to the page showing a list of posts with tag yii

2 indexphppost2A+Test+Post leads to the page showing the detail of the postwith ID 2 whose title is A Test Post

3 indexphppostupdate1 leads to the page that allows updating the post with ID1

Note that in the second URL format we include the post title in the URL This is mainlyto make the URL SEO friendly It is said that search engines may also respect the wordsfound in a URL when it is being indexed

To achieve our goal we modify the application configuration as follows

return array(

rsquocomponentsrsquo=gtarray(

rsquourlManagerrsquo=gtarray(

rsquourlFormatrsquo=gtrsquopathrsquo

rsquorulesrsquo=gtarray(

48 6 Final Work

rsquopostltidd+gtlttitlegtrsquo=gtrsquopostviewrsquorsquopostslttaggtrsquo=gtrsquopostindexrsquo

)

)

)

)

In the above we configure the urlManager component by setting its urlFormat propertyto be path and adding a set of rules

The rules are used by urlManager to parse and create the URLs in the desired formatFor example the first rule says that if a URL indexphppostsyii is requested theurlManager component should be responsible to dispatch the request to the route post

index and generate a tag GET parameter with the value yii On the other hand whencreating a URL with the route postindex and parameter tag the urlManager componentwill also use this rule to generate the desired URL indexphppostsyii For this reasonwe say that urlManager is a two-way URL manager

The urlManager component can further beautify our URLs such as hiding indexphp inthe URLs appending suffix like html to the URLs We can obtain these features easilyby configuring various properties of urlManager in the application configuration For moredetails please refer to the Guide

62 Logging Errors

A production Web application often needs sophisticated logging for various events In ourblog application we would like to log the errors occurring when it is being used Sucherrors could be programming mistakes or usersrsquo misuse of the system Logging these errorswill help us to improve the blog application

We enable the error logging by modifying the application configuration as follows

return array(

rsquopreloadrsquo=gtarray(rsquologrsquo)

rsquocomponentsrsquo=gtarray(

rsquologrsquo=gtarray(

rsquoclassrsquo=gtrsquoCLogRouterrsquo

rsquoroutesrsquo=gtarray(

array(

rsquoclassrsquo=gtrsquoCFileLogRoutersquo

rsquolevelsrsquo=gtrsquoerror warningrsquo

63 Final Tune-up and Deployment 49

)

)

)

)

)

With the above configuration if an error or warning occurs detailed information will belogged and saved in a file located under the directory wwwrootblogprotectedruntime

The log component offers more advanced features such as sending log messages to a listof email addresses displaying log messages in JavaScript console window etc For moredetails please refer to the Guide

63 Final Tune-up and Deployment

We are close to finish our blog application Before deployment we would like to do sometune-ups

631 Changing Home Page

We change to use the post list page as the home page We modify the application config-uration as follows

return array(

rsquodefaultControllerrsquo=gtrsquopostrsquo

)

Tip Because PostController already declares list to be its default action whenwe access the home page of the application we will see the result generated by thelist action of the post controller

632 Enabling Schema Caching

Because ActiveRecord relies on the metadata about tables to determine the column infor-mation it takes time to read the metadata and analyze it This may not be a problemduring development stage but for an application running in production mode it is a totalwaste of time if the database schema does not change Therefore we should enable theschema caching by modifying the application configuration as follows

50 6 Final Work

return array(

rsquocomponentsrsquo=gtarray(

rsquocachersquo=gtarray(

rsquoclassrsquo=gtrsquoCDbCachersquo

)

rsquodbrsquo=gtarray(

rsquoclassrsquo=gtrsquosystemdbCDbConnectionrsquo

rsquoconnectionStringrsquo=gtrsquosqlitewwwrootblogprotecteddatablogdbrsquo

rsquoschemaCachingDurationrsquo=gt3600

)

)

)

In the above we first add a cache component which uses a default SQLite database as thecaching storage If our server is equipped with other caching extensions such as APCwe could change to use them as well We also modify the db component by setting itsschemaCachingDuration property to be 3600 which means the parsed database schemadata can remain valid in cache for 3600 seconds

633 Disabling Debugging Mode

We modify the entry script file wwwrootblogindexphp by removing the line definingthe constant YII DEBUG This constant is useful during development stage because it allowsYii to display more debugging information when an error occurs However when theapplication is running in production mode displaying debugging information is not agood idea because it may contain sensitive information such as where the script file islocated and the content in the file etc

634 Deploying the Application

The final deployment process manly involves copying the directory wwwrootblog to thetarget directory The following checklist shows every needed step

1 Install Yii in the target place if it is not available

2 Copy the entire directory wwwrootblog to the target place

3 Edit the entry script file indexphp by pointing the $yii variable to the new Yiibootstrap file

4 Edit the file protectedyiicphp by setting the $yiic variable to be the new Yiiyiicphp file

64 Future Enhancements 51

5 Change the permission of the directories assets and protectedruntime so that theyare writable by the Web server process

64 Future Enhancements

641 Using a Theme

Without writing any code our blog application is already themeable To use a themewe mainly need to develop the theme by writing customized view files in the theme Forexample to use a theme named classic that uses a different page layout we would createa layout view file wwwrootblogthemesclassicviewslayoutsmainphp We also needto change the application configuration to indicate our choice of the classic theme

return array(

rsquothemersquo=gtrsquoclassicrsquo

)

642 Internationalization

We may also internationalize our blog application so that its pages can be displayed indifferent languages This mainly involves efforts in two aspects

First we may create view files in different languages For example for the list pageof PostController we can create a view file wwwrootblogprotectedviewspostzh cn

listphp When the application is configured to use simplified Chinese (the language codeis zh cn) Yii will automatically use this new view file instead of the original one

Second we may create message translations for those messages generated by code Themessage translations should be saved as files under the directory wwwrootblogprotected

messages We also need to modify the code where we use text strings by enclosing themin the method call Yiit()

For more details about internationalization please refer to the Guide

643 Improving Performance with Cache

While the Yii framework itself is very efficient it is not necessarily true that an applicationwritten in Yii efficient There are several places in our blog application that we can improvethe performance For example the tag clould portlet could be one of the performancebottlenecks because it involves complex database query and PHP logic

52 6 Final Work

We can make use of the sophisticated caching feature provided by Yii to improve theperformance One of the most useful components in Yii is COutputCache which caches afragment of page display so that the underlying code generating the fragment does not needto be executed for every request For example in the layout file wwwrootblogprotected

viewslayoutsmainphp we can enclose the tag cloud portlet with COutputCache

ltphp if($this-gtbeginCache(rsquotagCloudrsquo array(rsquodurationrsquo=gt3600))) gt

ltphp $this-gtwidget(rsquoTagCloudrsquo array(

rsquomaxTagsrsquo=gtYiiapp()-gtparams[rsquotagCloudCountrsquo]

)) gt

ltphp $this-gtendCache() gt

With the above code the tag cloud display will be served from cache instead of beinggenerated on-the-fly for every request The cached content will remain valid in cache for3600 seconds

644 Adding New Features

Our blog application only has very basic functionalities To become a complete blogsystem more features are needed for example calendar portlet email notifications postcategorization archived post portlet and so on We will leave the implementation of thesefeatures to interested readers

  • Contents
  • License
  • Getting Started
    • Building a Blog System using Yii
    • Testdriving with Yii
      • Installing Yii
      • Creating Skeleton Application
      • Application Workflow
        • Requirements Analysis
        • Overall Design
          • Initial Prototyping
            • Setting Up Database
              • Creating Database
              • Establishing Database Connection
                • Scaffolding
                • Authenticating User
                • Summary
                  • Post Management
                    • Customizing Post Model
                      • Customizing rules() Method
                      • Customizing relations() Method
                      • Representing Status in Text
                        • Creating and Updating Posts
                          • Customizing Access Control
                          • Customizing create and update Operations
                            • Displaying Posts
                              • Customizing view Operation
                              • Customizing index Operation
                                • Managing Posts
                                  • Listing Posts in Tabular View
                                  • Deleting Posts
                                      • Comment Management
                                        • Customizing Comment Model
                                          • Customizing rules() Method
                                          • Customizing attributeLabels() Method
                                          • Customizing Saving Process
                                            • Creating and Displaying Comments
                                              • Displaying Comments
                                              • Creating Comments
                                                • Managing Comments
                                                  • Updating and Deleting Comments
                                                  • Approving Comments
                                                      • Portlets
                                                        • Customizing Page Layout
                                                        • Creating User Menu Portlet
                                                          • Creating UserMenu Class
                                                          • Creating userMenu View
                                                          • Using UserMenu Portlet
                                                          • Testing UserMenu Portlet
                                                          • Summary
                                                            • Creating Tag Cloud Portlet
                                                              • Creating TagCloud Class
                                                              • Using TagCloud Portlet
                                                                • Creating Recent Comments Portlet
                                                                  • Creating RecentComments Class
                                                                  • Creating recentComments View
                                                                  • Using RecentComments Portlet
                                                                      • Final Work
                                                                        • Beautifying URLs
                                                                        • Logging Errors
                                                                        • Final Tune-up and Deployment
                                                                          • Changing Home Page
                                                                          • Enabling Schema Caching
                                                                          • Disabling Debugging Mode
                                                                          • Deploying the Application
                                                                            • Future Enhancements
                                                                              • Using a Theme
                                                                              • Internationalization
                                                                              • Improving Performance with Cache
                                                                              • Adding New Features
Page 42: Yii Blog 1.1.0 Demo Tutor

42 Creating and Displaying Comments 35

we display first a list of comments belonging to that post and then a comment creationform

In order to display comments on the post detail page we modify the view script wwwrootblogprotectedviewspostviewphp as follows

ltdiv id=commentsgt

ltphp if($model-gtcommentCountgt=1) gt

lth3gt

ltphp echo $model-gtcommentCount rsquocomment(s)rsquo gt

lth3gt

ltphp $this-gtrenderPartial(rsquo commentsrsquoarray(

rsquopostrsquo=gt$model

rsquocommentsrsquo=gt$model-gtcomments

)) gt

ltphp endif gt

ltdivgt

In the above we call renderPartial() to render a partial view named comments to displaythe list of comments belonging to the current post Note that in the view we use theexpression $model-gtcomments to retrieve the comments for the post This is valid becausewe have declared a comments relation in the Post class Evaluating this expression wouldtrigger an implicit JOIN database query to bring back the proper comments This featureis known as lazy relational query

The partial view comments is not very interesting It mainly goes through every commentand displays the detail of it Interested readers may refer to wwwrootyiidemosblog

protectedpost commentsphp

422 Creating Comments

To handle comment creation we first modify the actionView() method of PostController

as follows

public function actionView()

$post=$this-gtloadModel()

$comment=$this-gtnewComment($post)

$this-gtrender(rsquoviewrsquoarray(

rsquomodelrsquo=gt$post

rsquocommentrsquo=gt$comment

))

36 4 Comment Management

protected function newComment($post)

$comment=new Comment

if(isset($ POST[rsquoCommentrsquo]))

$comment-gtattributes=$ POST[rsquoCommentrsquo]

if($post-gtaddComment($comment))

if($comment-gtstatus==CommentSTATUS PENDING)

Yiiapp()-gtuser-gtsetFlash(rsquocommentSubmittedrsquorsquoThank yoursquo)

$this-gtrefresh()

return $comment

In the above we call the newComment() method before we render view In the newComment()

method we generate a Comment instance and check if the comment form is submitted If sowe try to add the comment for the post by calling $post-gtaddComment($comment) If it goesthrough we refresh the post detail page In case the comment needs to be approved we willshow a flash message to indicate this decision A flash message is usually a confirmationmessage displayed to end users If the user clicks on the refresh button of his browser themessage will disappear

We also need to modify wwwrootblogprotectedviewspostviewphp furthermore

ltdiv id=commentsgt

lth3gtLeave a Commentlth3gt

ltphp if(Yiiapp()-gtuser-gthasFlash(rsquocommentSubmittedrsquo)) gt

ltdiv class=successgt

ltphp echo Yiiapp()-gtuser-gtgetFlash(rsquocommentSubmittedrsquo) gt

ltdivgt

ltphp else gt

ltphp $this-gtrenderPartial(rsquocomment formrsquoarray(

rsquomodelrsquo=gt$comment

)) gt

ltphp endif gt

ltdivgtlt-- comments --gt

43 Managing Comments 37

In the above code we display the flash message if it is available If not we displaythe comment input form by rendering the partial view wwwrootblogprotectedviews

comment formphp

43 Managing Comments

Comment management includes updating deleting and approving comments These op-erations are implemented as actions in the CommentController class

431 Updating and Deleting Comments

The code generated by yiic for updating and deleting comments remains largely un-changed

432 Approving Comments

When comments are newly created they are in pending approval status and need to beapproved in order to be visible to guest users Approving a comment is mainly aboutchanging the status column of the comment

We create an actionApprove() method in CommentController as follows

public function actionApprove()

if(Yiiapp()-gtrequest-gtisPostRequest)

$comment=$this-gtloadModel()

$comment-gtapprove()

$this-gtredirect(array(rsquoindexrsquo))

else

throw new CHttpException(400rsquoInvalid requestrsquo)

In the above when the approve action is invoked via a POST request we call the approve()

method defined in the Comment model to change the status We then redirect the userbrowser to the page displaying the post that this comment belongs to

We also modify the actionIndex() method of Comment to show all comments We wouldlike to see comments pending approved to show up first

public function actionIndex()

38 4 Comment Management

$dataProvider=new CActiveDataProvider(rsquoCommentrsquo array(

rsquocriteriarsquo=gtarray(

rsquowithrsquo=gtrsquopostrsquo

rsquoorderrsquo=gtrsquotstatus tcreate time DESCrsquo

)

))

$this-gtrender(rsquoindexrsquoarray(

rsquodataProviderrsquo=gt$dataProvider

))

Notice that in the above code because both tbl post and tbl comment have columns statusand create time we need to disambiguate the corresponding column reference by prefixingthem with table alias names As described in the guide the alias for the primary table ina relational query is always t Therefore we are prefixing t to the status and create time

columns in the above code

Like the post index view the index view for CommentController uses [CListView] to displaythe comment list which in turn uses the partial view wwwrootblogprotectedviews

comment viewphp to display the detail of each individual comment We will not go intodetails here Interested readers may refer to the corresponding file in the blog demowwwrootyiidemosblogprotectedviewscomment viewphp

Chapter 5

Portlets

51 Customizing Page Layout

Features like rdquothe most recent commentsrdquo rdquotag cloudrdquo are better to be implemented inportlets A portlet is a pluggable user interface component that renders a fragment ofHTML code In this section we describe how to set up the portlet architecture for ourblog application

Before we go into details about developing portlets we first need to adjust our page layoutso that the portlets can be placed in a side bar

A page layout determines how to share common sections (eg header footer) amongdifferent pages and how to embed contents that are specific for different pages In Yiia page layout is represented as a view under the protectedviewslayouts folder Whenwe call the render() method in a controller the rendering result will be automaticallyembedded into this layout view before it is displayed to end users The default layoutview is named as main which means the wwwrootblogprotectedviewslayoutsmain

php file in our blog application

We could directly modify the main layout to add a side bar However doing so wouldmake some irrelevant pages such as about contact pages to have a side bar which is notalways desirable For this reason we need different layouts for different pages

We thus create two different layouts column1 and column2 The former displays the pagein a single column while the latter displays two columns in the page one of which is theside bar We will use column2 layout for the post and comment pages and column1 forthe rest of the pages Because both column1 and column2 still share the same header andfooter sections we let them to share the main layout

Based on the above description we create the column1 layout as follows

ltphp $this-gtbeginContent(rsquolayoutsmainrsquo) gt

ltdiv class=containergt

40 5 Portlets

ltdiv id=contentgt

ltphp echo $content gt

ltdivgtlt-- content --gt

ltdivgt

ltphp $this-gtendContent() gt

and the column2 layout as follows

ltphp $this-gtbeginContent(rsquolayoutsmainrsquo) gt

ltdiv class=containergt

ltdiv class=span-18gt

ltdiv id=contentgt

ltphp echo $content gt

ltdivgtlt-- content --gt

ltdivgt

ltdiv class=span-6 lastgt

ltdiv id=sidebargt

portlets to be placed here

ltdivgtlt-- sidebar --gt

ltdivgt

ltdivgt

ltphp $this-gtendContent() gt

As we can see both column1 and column2 are enclosed in a pair of beginContent andendContent calls They essentially use the specified view layoutsmain to decorate theenclosed content In all these layout views the variable $content refers to the dynamiccontent to be embedded into the layout

Besides customizing the layout view file we also need to adjust the CSS file wwwroot

blogcssmaincss so that the overall appearance would look like what we see in the blogdemo We will not go into details here

Info The skeleton application generated by the yiic tool uses the BluePrint CSSframework It provides a nice font reset and a robust grid system For more detailson how to use this framework please refer to its documentation

After creating the new layouts we need to set the layout property of controllers to makeuse of them So far we have three controllers SiteController PostController andCommentController We set SiteController to use column1 as the default layout whilethe rest two of the controllers use column2 for example

class PostController extends Controller

52 Creating User Menu Portlet 41

public $layout=rsquocolumn2rsquo

Tip If some view of the controller does not use the same layout as declaredby the layout property it may customize it in the view by a simple assignment$this-gtlayout=rsquonewlayoutrsquo

52 Creating User Menu Portlet

Based on the requirements analysis we need three different portlets the rdquouser menurdquoportlet the rdquotag cloudrdquo portlet and the rdquorecent commentsrdquo portlet We will develop theseportlets by extending the [CPortlet] widget provided by Yii

In this section we will develop our first concrete portlet - the user menu portlet whichdisplays a list of menu items that are only available to authenticated users The menucontains four items

bull Approve Comments a hyperlink that leads to a list of comments pending approval

bull Create New Post a hyperlink that leads to the post creation page

bull Manage Posts a hyperlink that leads to the post management page

bull Logout a link button that would log out the current user

521 Creating UserMenu Class

We create the UserMenu class to represent the logic part of the user menu portlet Theclass is saved in the file wwwrootblogprotectedcomponentsUserMenuphp which has thefollowing content

Yiiimport(rsquoziiwidgetsCPortletrsquo)

class UserMenu extends CPortlet

public function init()

$this-gttitle=CHtmlencode(Yiiapp()-gtuser-gtname)

parentinit()

42 5 Portlets

protected function renderContent()

$this-gtrender(rsquouserMenursquo)

The UserMenu class extends from the CPortlet class that we created previously It overridesboth the init() method and the renderContent() method of CPortlet The former setsthe portlet title to be the name of the current user the latter generates the portlet bodycontent by rendering a view named userMenu

Tip Notice that we have to explicitly include the CPortlet class by calling Yii

import() before we refer to it the first time This is because CPortlet is part of thezii project ndash the official extension library for Yii For performance considerationclasses in this project are not listed as core classes Therefore we have to import itbefore we use it the first time

522 Creating userMenu View

Next we create the userMenu view which is saved in the file wwwrootblogprotected

componentsviewsuserMenuphp

ltulgt

ltligtltphp echo CHtmllink(rsquoCreate New Postrsquoarray(rsquopostcreatersquo)) gtltligt

ltligtltphp echo CHtmllink(rsquoManage Postsrsquoarray(rsquopostadminrsquo)) gtltligt

ltligtltphp echo CHtmllink(rsquoApprove Commentsrsquoarray(rsquocommentindexrsquo))

rsquo (rsquo Commentmodel()-gtpendingCommentCount rsquo)rsquo gtltligt

ltligtltphp echo CHtmllink(rsquoLogoutrsquoarray(rsquositelogoutrsquo)) gtltligt

ltulgt

Info By default view files for a widget should be placed under the views sub-directory of the directory containing the widget class file The file name must bethe same as the view name

523 Using UserMenu Portlet

It is time for us to make use of our newly completed UserMenu portlet We modify thelayout view file wwwrootblogprotectedviewslayoutscolumn2php as follows

53 Creating Tag Cloud Portlet 43

ltdiv id=sidebargt

ltphp if(Yiiapp()-gtuser-gtisGuest) $this-gtwidget(rsquoUserMenursquo) gt

ltdivgt

In the above we call the widget() method to generate and execute an instance of theUserMenu class Because the portlet should only be displayed to authenticated users weonly call widget() when the isGuest property of the current user is false (meaning theuser is authenticated)

524 Testing UserMenu Portlet

Letrsquos test what we have so far

1 Open a browser window and enter the URL httpwwwexamplecomblogindex

php Verify that there is nothing displayed in the side bar section of the page

2 Click on the Login hyperlink and fill out the login form to login If successful verifythat the UserMenu portlet appears in the side bar and the portlet has the usernameas its title

3 Click on the rsquoLogoutrsquo hyperlink in the UserMenu portlet Verify that the logout actionis successful and the UserMenu portlet disappears

525 Summary

What we have created is a portlet that is highly reusable We can easily reuse it in adifferent project with little or no modification Moreover the design of this portlet followsclosely the philosophy that logic and presentation should be separated While we did notpoint this out in the previous sections such practice is used nearly everywhere in a typicalYii application

53 Creating Tag Cloud Portlet

Tag cloud displays a list of post tags with visual decorations hinting the popularity of eachindividual tag

531 Creating TagCloud Class

We create the TagCloud class in the file wwwrootblogprotectedcomponentsTagCloud

php The file has the following content

44 5 Portlets

Yiiimport(rsquoziiwidgetsCPortletrsquo)

class TagCloud extends CPortlet

public $title=rsquoTagsrsquo

public $maxTags=20

protected function renderContent()

$tags=Tagmodel()-gtfindTagWeights($this-gtmaxTags)

foreach($tags as $tag=gt$weight)

$link=CHtmllink(CHtmlencode($tag) array(rsquopostindexrsquorsquotagrsquo=gt$tag))

echo CHtmltag(rsquospanrsquo array(

rsquoclassrsquo=gtrsquotagrsquo

rsquostylersquo=gtfont-size$weightpt) $link)n

Unlike the UserMenu portlet the TagCloud portlet does not use a view Instead its presen-tation is done in the renderContent() method This is because the presentation does notcontain much HTML tags

We display each tag as a hyperlink to the post index page with the corresponding tagparameter The font size of each tag link is adjusted according to their relative weightamong other tags If a tag has higher frequency value than the other it will have a biggerfont size

532 Using TagCloud Portlet

Usage of the TagCloud portlet is very simple We modify the layout file wwwrootblog

protectedviewslayoutsmainphp as follows

ltdiv id=sidebargt

ltphp if(Yiiapp()-gtuser-gtisGuest) $this-gtwidget(rsquoUserMenursquo) gt

ltphp $this-gtwidget(rsquoTagCloudrsquo array(

rsquomaxTagsrsquo=gtYiiapp()-gtparams[rsquotagCloudCountrsquo]

)) gt

ltdivgt

54 Creating Recent Comments Portlet 45

54 Creating Recent Comments Portlet

In this section we create the last portlet that displays a list of comments recently pub-lished

541 Creating RecentComments Class

We create the RecentComments class in the file wwwrootblogprotectedcomponentsRecentCommentsphp The file has the following content

Yiiimport(rsquoziiwidgetsCPortletrsquo)

class RecentComments extends CPortlet

public $title=rsquoRecent Commentsrsquo

public $maxComments=10

public function getRecentComments()

return Commentmodel()-gtfindRecentComments($this-gtmaxComments)

protected function renderContent()

$this-gtrender(rsquorecentCommentsrsquo)

In the above we invoke the findRecentComments method which is defined in the Comment

class as follows

class Comment extends CActiveRecord

public function findRecentComments($limit=10)

return $this-gtwith(rsquopostrsquo)-gtfindAll(array(

rsquoconditionrsquo=gtrsquotstatus=rsquoselfSTATUS APPROVED

rsquoorderrsquo=gtrsquotcreate time DESCrsquo

rsquolimitrsquo=gt$limit

))

46 5 Portlets

542 Creating recentComments View

The recentComments view is saved in the file wwwrootblogprotectedcomponentsviews

recentCommentsphp It simply displays every comment returned by the RecentComments

getRecentComments() method

543 Using RecentComments Portlet

We modify the layout file wwwrootblogprotectedviewslayoutsmainphp to embedthis last portlet

ltdiv id=sidebargt

ltphp if(Yiiapp()-gtuser-gtisGuest) $this-gtwidget(rsquoUserMenursquo) gt

ltphp $this-gtwidget(rsquoTagCloudrsquo array(

rsquomaxTagsrsquo=gtYiiapp()-gtparams[rsquotagCloudCountrsquo]

)) gt

ltphp $this-gtwidget(rsquoRecentCommentsrsquo array(

rsquomaxCommentsrsquo=gtYiiapp()-gtparams[rsquorecentCommentCountrsquo]

)) gt

ltdivgt

Chapter 6

Final Work

61 Beautifying URLs

The URLs linking various pages of our blog application currently look ugly For examplethe URL for the page showing a post looks like the following

indexphpr=postshowampid=1

In this section we describe how to beautify these URLs and make them SEO-friendlyOur goal is to be able to use the following URLs in the application

1 indexphppostsyii leads to the page showing a list of posts with tag yii

2 indexphppost2A+Test+Post leads to the page showing the detail of the postwith ID 2 whose title is A Test Post

3 indexphppostupdate1 leads to the page that allows updating the post with ID1

Note that in the second URL format we include the post title in the URL This is mainlyto make the URL SEO friendly It is said that search engines may also respect the wordsfound in a URL when it is being indexed

To achieve our goal we modify the application configuration as follows

return array(

rsquocomponentsrsquo=gtarray(

rsquourlManagerrsquo=gtarray(

rsquourlFormatrsquo=gtrsquopathrsquo

rsquorulesrsquo=gtarray(

48 6 Final Work

rsquopostltidd+gtlttitlegtrsquo=gtrsquopostviewrsquorsquopostslttaggtrsquo=gtrsquopostindexrsquo

)

)

)

)

In the above we configure the urlManager component by setting its urlFormat propertyto be path and adding a set of rules

The rules are used by urlManager to parse and create the URLs in the desired formatFor example the first rule says that if a URL indexphppostsyii is requested theurlManager component should be responsible to dispatch the request to the route post

index and generate a tag GET parameter with the value yii On the other hand whencreating a URL with the route postindex and parameter tag the urlManager componentwill also use this rule to generate the desired URL indexphppostsyii For this reasonwe say that urlManager is a two-way URL manager

The urlManager component can further beautify our URLs such as hiding indexphp inthe URLs appending suffix like html to the URLs We can obtain these features easilyby configuring various properties of urlManager in the application configuration For moredetails please refer to the Guide

62 Logging Errors

A production Web application often needs sophisticated logging for various events In ourblog application we would like to log the errors occurring when it is being used Sucherrors could be programming mistakes or usersrsquo misuse of the system Logging these errorswill help us to improve the blog application

We enable the error logging by modifying the application configuration as follows

return array(

rsquopreloadrsquo=gtarray(rsquologrsquo)

rsquocomponentsrsquo=gtarray(

rsquologrsquo=gtarray(

rsquoclassrsquo=gtrsquoCLogRouterrsquo

rsquoroutesrsquo=gtarray(

array(

rsquoclassrsquo=gtrsquoCFileLogRoutersquo

rsquolevelsrsquo=gtrsquoerror warningrsquo

63 Final Tune-up and Deployment 49

)

)

)

)

)

With the above configuration if an error or warning occurs detailed information will belogged and saved in a file located under the directory wwwrootblogprotectedruntime

The log component offers more advanced features such as sending log messages to a listof email addresses displaying log messages in JavaScript console window etc For moredetails please refer to the Guide

63 Final Tune-up and Deployment

We are close to finish our blog application Before deployment we would like to do sometune-ups

631 Changing Home Page

We change to use the post list page as the home page We modify the application config-uration as follows

return array(

rsquodefaultControllerrsquo=gtrsquopostrsquo

)

Tip Because PostController already declares list to be its default action whenwe access the home page of the application we will see the result generated by thelist action of the post controller

632 Enabling Schema Caching

Because ActiveRecord relies on the metadata about tables to determine the column infor-mation it takes time to read the metadata and analyze it This may not be a problemduring development stage but for an application running in production mode it is a totalwaste of time if the database schema does not change Therefore we should enable theschema caching by modifying the application configuration as follows

50 6 Final Work

return array(

rsquocomponentsrsquo=gtarray(

rsquocachersquo=gtarray(

rsquoclassrsquo=gtrsquoCDbCachersquo

)

rsquodbrsquo=gtarray(

rsquoclassrsquo=gtrsquosystemdbCDbConnectionrsquo

rsquoconnectionStringrsquo=gtrsquosqlitewwwrootblogprotecteddatablogdbrsquo

rsquoschemaCachingDurationrsquo=gt3600

)

)

)

In the above we first add a cache component which uses a default SQLite database as thecaching storage If our server is equipped with other caching extensions such as APCwe could change to use them as well We also modify the db component by setting itsschemaCachingDuration property to be 3600 which means the parsed database schemadata can remain valid in cache for 3600 seconds

633 Disabling Debugging Mode

We modify the entry script file wwwrootblogindexphp by removing the line definingthe constant YII DEBUG This constant is useful during development stage because it allowsYii to display more debugging information when an error occurs However when theapplication is running in production mode displaying debugging information is not agood idea because it may contain sensitive information such as where the script file islocated and the content in the file etc

634 Deploying the Application

The final deployment process manly involves copying the directory wwwrootblog to thetarget directory The following checklist shows every needed step

1 Install Yii in the target place if it is not available

2 Copy the entire directory wwwrootblog to the target place

3 Edit the entry script file indexphp by pointing the $yii variable to the new Yiibootstrap file

4 Edit the file protectedyiicphp by setting the $yiic variable to be the new Yiiyiicphp file

64 Future Enhancements 51

5 Change the permission of the directories assets and protectedruntime so that theyare writable by the Web server process

64 Future Enhancements

641 Using a Theme

Without writing any code our blog application is already themeable To use a themewe mainly need to develop the theme by writing customized view files in the theme Forexample to use a theme named classic that uses a different page layout we would createa layout view file wwwrootblogthemesclassicviewslayoutsmainphp We also needto change the application configuration to indicate our choice of the classic theme

return array(

rsquothemersquo=gtrsquoclassicrsquo

)

642 Internationalization

We may also internationalize our blog application so that its pages can be displayed indifferent languages This mainly involves efforts in two aspects

First we may create view files in different languages For example for the list pageof PostController we can create a view file wwwrootblogprotectedviewspostzh cn

listphp When the application is configured to use simplified Chinese (the language codeis zh cn) Yii will automatically use this new view file instead of the original one

Second we may create message translations for those messages generated by code Themessage translations should be saved as files under the directory wwwrootblogprotected

messages We also need to modify the code where we use text strings by enclosing themin the method call Yiit()

For more details about internationalization please refer to the Guide

643 Improving Performance with Cache

While the Yii framework itself is very efficient it is not necessarily true that an applicationwritten in Yii efficient There are several places in our blog application that we can improvethe performance For example the tag clould portlet could be one of the performancebottlenecks because it involves complex database query and PHP logic

52 6 Final Work

We can make use of the sophisticated caching feature provided by Yii to improve theperformance One of the most useful components in Yii is COutputCache which caches afragment of page display so that the underlying code generating the fragment does not needto be executed for every request For example in the layout file wwwrootblogprotected

viewslayoutsmainphp we can enclose the tag cloud portlet with COutputCache

ltphp if($this-gtbeginCache(rsquotagCloudrsquo array(rsquodurationrsquo=gt3600))) gt

ltphp $this-gtwidget(rsquoTagCloudrsquo array(

rsquomaxTagsrsquo=gtYiiapp()-gtparams[rsquotagCloudCountrsquo]

)) gt

ltphp $this-gtendCache() gt

With the above code the tag cloud display will be served from cache instead of beinggenerated on-the-fly for every request The cached content will remain valid in cache for3600 seconds

644 Adding New Features

Our blog application only has very basic functionalities To become a complete blogsystem more features are needed for example calendar portlet email notifications postcategorization archived post portlet and so on We will leave the implementation of thesefeatures to interested readers

  • Contents
  • License
  • Getting Started
    • Building a Blog System using Yii
    • Testdriving with Yii
      • Installing Yii
      • Creating Skeleton Application
      • Application Workflow
        • Requirements Analysis
        • Overall Design
          • Initial Prototyping
            • Setting Up Database
              • Creating Database
              • Establishing Database Connection
                • Scaffolding
                • Authenticating User
                • Summary
                  • Post Management
                    • Customizing Post Model
                      • Customizing rules() Method
                      • Customizing relations() Method
                      • Representing Status in Text
                        • Creating and Updating Posts
                          • Customizing Access Control
                          • Customizing create and update Operations
                            • Displaying Posts
                              • Customizing view Operation
                              • Customizing index Operation
                                • Managing Posts
                                  • Listing Posts in Tabular View
                                  • Deleting Posts
                                      • Comment Management
                                        • Customizing Comment Model
                                          • Customizing rules() Method
                                          • Customizing attributeLabels() Method
                                          • Customizing Saving Process
                                            • Creating and Displaying Comments
                                              • Displaying Comments
                                              • Creating Comments
                                                • Managing Comments
                                                  • Updating and Deleting Comments
                                                  • Approving Comments
                                                      • Portlets
                                                        • Customizing Page Layout
                                                        • Creating User Menu Portlet
                                                          • Creating UserMenu Class
                                                          • Creating userMenu View
                                                          • Using UserMenu Portlet
                                                          • Testing UserMenu Portlet
                                                          • Summary
                                                            • Creating Tag Cloud Portlet
                                                              • Creating TagCloud Class
                                                              • Using TagCloud Portlet
                                                                • Creating Recent Comments Portlet
                                                                  • Creating RecentComments Class
                                                                  • Creating recentComments View
                                                                  • Using RecentComments Portlet
                                                                      • Final Work
                                                                        • Beautifying URLs
                                                                        • Logging Errors
                                                                        • Final Tune-up and Deployment
                                                                          • Changing Home Page
                                                                          • Enabling Schema Caching
                                                                          • Disabling Debugging Mode
                                                                          • Deploying the Application
                                                                            • Future Enhancements
                                                                              • Using a Theme
                                                                              • Internationalization
                                                                              • Improving Performance with Cache
                                                                              • Adding New Features
Page 43: Yii Blog 1.1.0 Demo Tutor

36 4 Comment Management

protected function newComment($post)

$comment=new Comment

if(isset($ POST[rsquoCommentrsquo]))

$comment-gtattributes=$ POST[rsquoCommentrsquo]

if($post-gtaddComment($comment))

if($comment-gtstatus==CommentSTATUS PENDING)

Yiiapp()-gtuser-gtsetFlash(rsquocommentSubmittedrsquorsquoThank yoursquo)

$this-gtrefresh()

return $comment

In the above we call the newComment() method before we render view In the newComment()

method we generate a Comment instance and check if the comment form is submitted If sowe try to add the comment for the post by calling $post-gtaddComment($comment) If it goesthrough we refresh the post detail page In case the comment needs to be approved we willshow a flash message to indicate this decision A flash message is usually a confirmationmessage displayed to end users If the user clicks on the refresh button of his browser themessage will disappear

We also need to modify wwwrootblogprotectedviewspostviewphp furthermore

ltdiv id=commentsgt

lth3gtLeave a Commentlth3gt

ltphp if(Yiiapp()-gtuser-gthasFlash(rsquocommentSubmittedrsquo)) gt

ltdiv class=successgt

ltphp echo Yiiapp()-gtuser-gtgetFlash(rsquocommentSubmittedrsquo) gt

ltdivgt

ltphp else gt

ltphp $this-gtrenderPartial(rsquocomment formrsquoarray(

rsquomodelrsquo=gt$comment

)) gt

ltphp endif gt

ltdivgtlt-- comments --gt

43 Managing Comments 37

In the above code we display the flash message if it is available If not we displaythe comment input form by rendering the partial view wwwrootblogprotectedviews

comment formphp

43 Managing Comments

Comment management includes updating deleting and approving comments These op-erations are implemented as actions in the CommentController class

431 Updating and Deleting Comments

The code generated by yiic for updating and deleting comments remains largely un-changed

432 Approving Comments

When comments are newly created they are in pending approval status and need to beapproved in order to be visible to guest users Approving a comment is mainly aboutchanging the status column of the comment

We create an actionApprove() method in CommentController as follows

public function actionApprove()

if(Yiiapp()-gtrequest-gtisPostRequest)

$comment=$this-gtloadModel()

$comment-gtapprove()

$this-gtredirect(array(rsquoindexrsquo))

else

throw new CHttpException(400rsquoInvalid requestrsquo)

In the above when the approve action is invoked via a POST request we call the approve()

method defined in the Comment model to change the status We then redirect the userbrowser to the page displaying the post that this comment belongs to

We also modify the actionIndex() method of Comment to show all comments We wouldlike to see comments pending approved to show up first

public function actionIndex()

38 4 Comment Management

$dataProvider=new CActiveDataProvider(rsquoCommentrsquo array(

rsquocriteriarsquo=gtarray(

rsquowithrsquo=gtrsquopostrsquo

rsquoorderrsquo=gtrsquotstatus tcreate time DESCrsquo

)

))

$this-gtrender(rsquoindexrsquoarray(

rsquodataProviderrsquo=gt$dataProvider

))

Notice that in the above code because both tbl post and tbl comment have columns statusand create time we need to disambiguate the corresponding column reference by prefixingthem with table alias names As described in the guide the alias for the primary table ina relational query is always t Therefore we are prefixing t to the status and create time

columns in the above code

Like the post index view the index view for CommentController uses [CListView] to displaythe comment list which in turn uses the partial view wwwrootblogprotectedviews

comment viewphp to display the detail of each individual comment We will not go intodetails here Interested readers may refer to the corresponding file in the blog demowwwrootyiidemosblogprotectedviewscomment viewphp

Chapter 5

Portlets

51 Customizing Page Layout

Features like rdquothe most recent commentsrdquo rdquotag cloudrdquo are better to be implemented inportlets A portlet is a pluggable user interface component that renders a fragment ofHTML code In this section we describe how to set up the portlet architecture for ourblog application

Before we go into details about developing portlets we first need to adjust our page layoutso that the portlets can be placed in a side bar

A page layout determines how to share common sections (eg header footer) amongdifferent pages and how to embed contents that are specific for different pages In Yiia page layout is represented as a view under the protectedviewslayouts folder Whenwe call the render() method in a controller the rendering result will be automaticallyembedded into this layout view before it is displayed to end users The default layoutview is named as main which means the wwwrootblogprotectedviewslayoutsmain

php file in our blog application

We could directly modify the main layout to add a side bar However doing so wouldmake some irrelevant pages such as about contact pages to have a side bar which is notalways desirable For this reason we need different layouts for different pages

We thus create two different layouts column1 and column2 The former displays the pagein a single column while the latter displays two columns in the page one of which is theside bar We will use column2 layout for the post and comment pages and column1 forthe rest of the pages Because both column1 and column2 still share the same header andfooter sections we let them to share the main layout

Based on the above description we create the column1 layout as follows

ltphp $this-gtbeginContent(rsquolayoutsmainrsquo) gt

ltdiv class=containergt

40 5 Portlets

ltdiv id=contentgt

ltphp echo $content gt

ltdivgtlt-- content --gt

ltdivgt

ltphp $this-gtendContent() gt

and the column2 layout as follows

ltphp $this-gtbeginContent(rsquolayoutsmainrsquo) gt

ltdiv class=containergt

ltdiv class=span-18gt

ltdiv id=contentgt

ltphp echo $content gt

ltdivgtlt-- content --gt

ltdivgt

ltdiv class=span-6 lastgt

ltdiv id=sidebargt

portlets to be placed here

ltdivgtlt-- sidebar --gt

ltdivgt

ltdivgt

ltphp $this-gtendContent() gt

As we can see both column1 and column2 are enclosed in a pair of beginContent andendContent calls They essentially use the specified view layoutsmain to decorate theenclosed content In all these layout views the variable $content refers to the dynamiccontent to be embedded into the layout

Besides customizing the layout view file we also need to adjust the CSS file wwwroot

blogcssmaincss so that the overall appearance would look like what we see in the blogdemo We will not go into details here

Info The skeleton application generated by the yiic tool uses the BluePrint CSSframework It provides a nice font reset and a robust grid system For more detailson how to use this framework please refer to its documentation

After creating the new layouts we need to set the layout property of controllers to makeuse of them So far we have three controllers SiteController PostController andCommentController We set SiteController to use column1 as the default layout whilethe rest two of the controllers use column2 for example

class PostController extends Controller

52 Creating User Menu Portlet 41

public $layout=rsquocolumn2rsquo

Tip If some view of the controller does not use the same layout as declaredby the layout property it may customize it in the view by a simple assignment$this-gtlayout=rsquonewlayoutrsquo

52 Creating User Menu Portlet

Based on the requirements analysis we need three different portlets the rdquouser menurdquoportlet the rdquotag cloudrdquo portlet and the rdquorecent commentsrdquo portlet We will develop theseportlets by extending the [CPortlet] widget provided by Yii

In this section we will develop our first concrete portlet - the user menu portlet whichdisplays a list of menu items that are only available to authenticated users The menucontains four items

bull Approve Comments a hyperlink that leads to a list of comments pending approval

bull Create New Post a hyperlink that leads to the post creation page

bull Manage Posts a hyperlink that leads to the post management page

bull Logout a link button that would log out the current user

521 Creating UserMenu Class

We create the UserMenu class to represent the logic part of the user menu portlet Theclass is saved in the file wwwrootblogprotectedcomponentsUserMenuphp which has thefollowing content

Yiiimport(rsquoziiwidgetsCPortletrsquo)

class UserMenu extends CPortlet

public function init()

$this-gttitle=CHtmlencode(Yiiapp()-gtuser-gtname)

parentinit()

42 5 Portlets

protected function renderContent()

$this-gtrender(rsquouserMenursquo)

The UserMenu class extends from the CPortlet class that we created previously It overridesboth the init() method and the renderContent() method of CPortlet The former setsthe portlet title to be the name of the current user the latter generates the portlet bodycontent by rendering a view named userMenu

Tip Notice that we have to explicitly include the CPortlet class by calling Yii

import() before we refer to it the first time This is because CPortlet is part of thezii project ndash the official extension library for Yii For performance considerationclasses in this project are not listed as core classes Therefore we have to import itbefore we use it the first time

522 Creating userMenu View

Next we create the userMenu view which is saved in the file wwwrootblogprotected

componentsviewsuserMenuphp

ltulgt

ltligtltphp echo CHtmllink(rsquoCreate New Postrsquoarray(rsquopostcreatersquo)) gtltligt

ltligtltphp echo CHtmllink(rsquoManage Postsrsquoarray(rsquopostadminrsquo)) gtltligt

ltligtltphp echo CHtmllink(rsquoApprove Commentsrsquoarray(rsquocommentindexrsquo))

rsquo (rsquo Commentmodel()-gtpendingCommentCount rsquo)rsquo gtltligt

ltligtltphp echo CHtmllink(rsquoLogoutrsquoarray(rsquositelogoutrsquo)) gtltligt

ltulgt

Info By default view files for a widget should be placed under the views sub-directory of the directory containing the widget class file The file name must bethe same as the view name

523 Using UserMenu Portlet

It is time for us to make use of our newly completed UserMenu portlet We modify thelayout view file wwwrootblogprotectedviewslayoutscolumn2php as follows

53 Creating Tag Cloud Portlet 43

ltdiv id=sidebargt

ltphp if(Yiiapp()-gtuser-gtisGuest) $this-gtwidget(rsquoUserMenursquo) gt

ltdivgt

In the above we call the widget() method to generate and execute an instance of theUserMenu class Because the portlet should only be displayed to authenticated users weonly call widget() when the isGuest property of the current user is false (meaning theuser is authenticated)

524 Testing UserMenu Portlet

Letrsquos test what we have so far

1 Open a browser window and enter the URL httpwwwexamplecomblogindex

php Verify that there is nothing displayed in the side bar section of the page

2 Click on the Login hyperlink and fill out the login form to login If successful verifythat the UserMenu portlet appears in the side bar and the portlet has the usernameas its title

3 Click on the rsquoLogoutrsquo hyperlink in the UserMenu portlet Verify that the logout actionis successful and the UserMenu portlet disappears

525 Summary

What we have created is a portlet that is highly reusable We can easily reuse it in adifferent project with little or no modification Moreover the design of this portlet followsclosely the philosophy that logic and presentation should be separated While we did notpoint this out in the previous sections such practice is used nearly everywhere in a typicalYii application

53 Creating Tag Cloud Portlet

Tag cloud displays a list of post tags with visual decorations hinting the popularity of eachindividual tag

531 Creating TagCloud Class

We create the TagCloud class in the file wwwrootblogprotectedcomponentsTagCloud

php The file has the following content

44 5 Portlets

Yiiimport(rsquoziiwidgetsCPortletrsquo)

class TagCloud extends CPortlet

public $title=rsquoTagsrsquo

public $maxTags=20

protected function renderContent()

$tags=Tagmodel()-gtfindTagWeights($this-gtmaxTags)

foreach($tags as $tag=gt$weight)

$link=CHtmllink(CHtmlencode($tag) array(rsquopostindexrsquorsquotagrsquo=gt$tag))

echo CHtmltag(rsquospanrsquo array(

rsquoclassrsquo=gtrsquotagrsquo

rsquostylersquo=gtfont-size$weightpt) $link)n

Unlike the UserMenu portlet the TagCloud portlet does not use a view Instead its presen-tation is done in the renderContent() method This is because the presentation does notcontain much HTML tags

We display each tag as a hyperlink to the post index page with the corresponding tagparameter The font size of each tag link is adjusted according to their relative weightamong other tags If a tag has higher frequency value than the other it will have a biggerfont size

532 Using TagCloud Portlet

Usage of the TagCloud portlet is very simple We modify the layout file wwwrootblog

protectedviewslayoutsmainphp as follows

ltdiv id=sidebargt

ltphp if(Yiiapp()-gtuser-gtisGuest) $this-gtwidget(rsquoUserMenursquo) gt

ltphp $this-gtwidget(rsquoTagCloudrsquo array(

rsquomaxTagsrsquo=gtYiiapp()-gtparams[rsquotagCloudCountrsquo]

)) gt

ltdivgt

54 Creating Recent Comments Portlet 45

54 Creating Recent Comments Portlet

In this section we create the last portlet that displays a list of comments recently pub-lished

541 Creating RecentComments Class

We create the RecentComments class in the file wwwrootblogprotectedcomponentsRecentCommentsphp The file has the following content

Yiiimport(rsquoziiwidgetsCPortletrsquo)

class RecentComments extends CPortlet

public $title=rsquoRecent Commentsrsquo

public $maxComments=10

public function getRecentComments()

return Commentmodel()-gtfindRecentComments($this-gtmaxComments)

protected function renderContent()

$this-gtrender(rsquorecentCommentsrsquo)

In the above we invoke the findRecentComments method which is defined in the Comment

class as follows

class Comment extends CActiveRecord

public function findRecentComments($limit=10)

return $this-gtwith(rsquopostrsquo)-gtfindAll(array(

rsquoconditionrsquo=gtrsquotstatus=rsquoselfSTATUS APPROVED

rsquoorderrsquo=gtrsquotcreate time DESCrsquo

rsquolimitrsquo=gt$limit

))

46 5 Portlets

542 Creating recentComments View

The recentComments view is saved in the file wwwrootblogprotectedcomponentsviews

recentCommentsphp It simply displays every comment returned by the RecentComments

getRecentComments() method

543 Using RecentComments Portlet

We modify the layout file wwwrootblogprotectedviewslayoutsmainphp to embedthis last portlet

ltdiv id=sidebargt

ltphp if(Yiiapp()-gtuser-gtisGuest) $this-gtwidget(rsquoUserMenursquo) gt

ltphp $this-gtwidget(rsquoTagCloudrsquo array(

rsquomaxTagsrsquo=gtYiiapp()-gtparams[rsquotagCloudCountrsquo]

)) gt

ltphp $this-gtwidget(rsquoRecentCommentsrsquo array(

rsquomaxCommentsrsquo=gtYiiapp()-gtparams[rsquorecentCommentCountrsquo]

)) gt

ltdivgt

Chapter 6

Final Work

61 Beautifying URLs

The URLs linking various pages of our blog application currently look ugly For examplethe URL for the page showing a post looks like the following

indexphpr=postshowampid=1

In this section we describe how to beautify these URLs and make them SEO-friendlyOur goal is to be able to use the following URLs in the application

1 indexphppostsyii leads to the page showing a list of posts with tag yii

2 indexphppost2A+Test+Post leads to the page showing the detail of the postwith ID 2 whose title is A Test Post

3 indexphppostupdate1 leads to the page that allows updating the post with ID1

Note that in the second URL format we include the post title in the URL This is mainlyto make the URL SEO friendly It is said that search engines may also respect the wordsfound in a URL when it is being indexed

To achieve our goal we modify the application configuration as follows

return array(

rsquocomponentsrsquo=gtarray(

rsquourlManagerrsquo=gtarray(

rsquourlFormatrsquo=gtrsquopathrsquo

rsquorulesrsquo=gtarray(

48 6 Final Work

rsquopostltidd+gtlttitlegtrsquo=gtrsquopostviewrsquorsquopostslttaggtrsquo=gtrsquopostindexrsquo

)

)

)

)

In the above we configure the urlManager component by setting its urlFormat propertyto be path and adding a set of rules

The rules are used by urlManager to parse and create the URLs in the desired formatFor example the first rule says that if a URL indexphppostsyii is requested theurlManager component should be responsible to dispatch the request to the route post

index and generate a tag GET parameter with the value yii On the other hand whencreating a URL with the route postindex and parameter tag the urlManager componentwill also use this rule to generate the desired URL indexphppostsyii For this reasonwe say that urlManager is a two-way URL manager

The urlManager component can further beautify our URLs such as hiding indexphp inthe URLs appending suffix like html to the URLs We can obtain these features easilyby configuring various properties of urlManager in the application configuration For moredetails please refer to the Guide

62 Logging Errors

A production Web application often needs sophisticated logging for various events In ourblog application we would like to log the errors occurring when it is being used Sucherrors could be programming mistakes or usersrsquo misuse of the system Logging these errorswill help us to improve the blog application

We enable the error logging by modifying the application configuration as follows

return array(

rsquopreloadrsquo=gtarray(rsquologrsquo)

rsquocomponentsrsquo=gtarray(

rsquologrsquo=gtarray(

rsquoclassrsquo=gtrsquoCLogRouterrsquo

rsquoroutesrsquo=gtarray(

array(

rsquoclassrsquo=gtrsquoCFileLogRoutersquo

rsquolevelsrsquo=gtrsquoerror warningrsquo

63 Final Tune-up and Deployment 49

)

)

)

)

)

With the above configuration if an error or warning occurs detailed information will belogged and saved in a file located under the directory wwwrootblogprotectedruntime

The log component offers more advanced features such as sending log messages to a listof email addresses displaying log messages in JavaScript console window etc For moredetails please refer to the Guide

63 Final Tune-up and Deployment

We are close to finish our blog application Before deployment we would like to do sometune-ups

631 Changing Home Page

We change to use the post list page as the home page We modify the application config-uration as follows

return array(

rsquodefaultControllerrsquo=gtrsquopostrsquo

)

Tip Because PostController already declares list to be its default action whenwe access the home page of the application we will see the result generated by thelist action of the post controller

632 Enabling Schema Caching

Because ActiveRecord relies on the metadata about tables to determine the column infor-mation it takes time to read the metadata and analyze it This may not be a problemduring development stage but for an application running in production mode it is a totalwaste of time if the database schema does not change Therefore we should enable theschema caching by modifying the application configuration as follows

50 6 Final Work

return array(

rsquocomponentsrsquo=gtarray(

rsquocachersquo=gtarray(

rsquoclassrsquo=gtrsquoCDbCachersquo

)

rsquodbrsquo=gtarray(

rsquoclassrsquo=gtrsquosystemdbCDbConnectionrsquo

rsquoconnectionStringrsquo=gtrsquosqlitewwwrootblogprotecteddatablogdbrsquo

rsquoschemaCachingDurationrsquo=gt3600

)

)

)

In the above we first add a cache component which uses a default SQLite database as thecaching storage If our server is equipped with other caching extensions such as APCwe could change to use them as well We also modify the db component by setting itsschemaCachingDuration property to be 3600 which means the parsed database schemadata can remain valid in cache for 3600 seconds

633 Disabling Debugging Mode

We modify the entry script file wwwrootblogindexphp by removing the line definingthe constant YII DEBUG This constant is useful during development stage because it allowsYii to display more debugging information when an error occurs However when theapplication is running in production mode displaying debugging information is not agood idea because it may contain sensitive information such as where the script file islocated and the content in the file etc

634 Deploying the Application

The final deployment process manly involves copying the directory wwwrootblog to thetarget directory The following checklist shows every needed step

1 Install Yii in the target place if it is not available

2 Copy the entire directory wwwrootblog to the target place

3 Edit the entry script file indexphp by pointing the $yii variable to the new Yiibootstrap file

4 Edit the file protectedyiicphp by setting the $yiic variable to be the new Yiiyiicphp file

64 Future Enhancements 51

5 Change the permission of the directories assets and protectedruntime so that theyare writable by the Web server process

64 Future Enhancements

641 Using a Theme

Without writing any code our blog application is already themeable To use a themewe mainly need to develop the theme by writing customized view files in the theme Forexample to use a theme named classic that uses a different page layout we would createa layout view file wwwrootblogthemesclassicviewslayoutsmainphp We also needto change the application configuration to indicate our choice of the classic theme

return array(

rsquothemersquo=gtrsquoclassicrsquo

)

642 Internationalization

We may also internationalize our blog application so that its pages can be displayed indifferent languages This mainly involves efforts in two aspects

First we may create view files in different languages For example for the list pageof PostController we can create a view file wwwrootblogprotectedviewspostzh cn

listphp When the application is configured to use simplified Chinese (the language codeis zh cn) Yii will automatically use this new view file instead of the original one

Second we may create message translations for those messages generated by code Themessage translations should be saved as files under the directory wwwrootblogprotected

messages We also need to modify the code where we use text strings by enclosing themin the method call Yiit()

For more details about internationalization please refer to the Guide

643 Improving Performance with Cache

While the Yii framework itself is very efficient it is not necessarily true that an applicationwritten in Yii efficient There are several places in our blog application that we can improvethe performance For example the tag clould portlet could be one of the performancebottlenecks because it involves complex database query and PHP logic

52 6 Final Work

We can make use of the sophisticated caching feature provided by Yii to improve theperformance One of the most useful components in Yii is COutputCache which caches afragment of page display so that the underlying code generating the fragment does not needto be executed for every request For example in the layout file wwwrootblogprotected

viewslayoutsmainphp we can enclose the tag cloud portlet with COutputCache

ltphp if($this-gtbeginCache(rsquotagCloudrsquo array(rsquodurationrsquo=gt3600))) gt

ltphp $this-gtwidget(rsquoTagCloudrsquo array(

rsquomaxTagsrsquo=gtYiiapp()-gtparams[rsquotagCloudCountrsquo]

)) gt

ltphp $this-gtendCache() gt

With the above code the tag cloud display will be served from cache instead of beinggenerated on-the-fly for every request The cached content will remain valid in cache for3600 seconds

644 Adding New Features

Our blog application only has very basic functionalities To become a complete blogsystem more features are needed for example calendar portlet email notifications postcategorization archived post portlet and so on We will leave the implementation of thesefeatures to interested readers

  • Contents
  • License
  • Getting Started
    • Building a Blog System using Yii
    • Testdriving with Yii
      • Installing Yii
      • Creating Skeleton Application
      • Application Workflow
        • Requirements Analysis
        • Overall Design
          • Initial Prototyping
            • Setting Up Database
              • Creating Database
              • Establishing Database Connection
                • Scaffolding
                • Authenticating User
                • Summary
                  • Post Management
                    • Customizing Post Model
                      • Customizing rules() Method
                      • Customizing relations() Method
                      • Representing Status in Text
                        • Creating and Updating Posts
                          • Customizing Access Control
                          • Customizing create and update Operations
                            • Displaying Posts
                              • Customizing view Operation
                              • Customizing index Operation
                                • Managing Posts
                                  • Listing Posts in Tabular View
                                  • Deleting Posts
                                      • Comment Management
                                        • Customizing Comment Model
                                          • Customizing rules() Method
                                          • Customizing attributeLabels() Method
                                          • Customizing Saving Process
                                            • Creating and Displaying Comments
                                              • Displaying Comments
                                              • Creating Comments
                                                • Managing Comments
                                                  • Updating and Deleting Comments
                                                  • Approving Comments
                                                      • Portlets
                                                        • Customizing Page Layout
                                                        • Creating User Menu Portlet
                                                          • Creating UserMenu Class
                                                          • Creating userMenu View
                                                          • Using UserMenu Portlet
                                                          • Testing UserMenu Portlet
                                                          • Summary
                                                            • Creating Tag Cloud Portlet
                                                              • Creating TagCloud Class
                                                              • Using TagCloud Portlet
                                                                • Creating Recent Comments Portlet
                                                                  • Creating RecentComments Class
                                                                  • Creating recentComments View
                                                                  • Using RecentComments Portlet
                                                                      • Final Work
                                                                        • Beautifying URLs
                                                                        • Logging Errors
                                                                        • Final Tune-up and Deployment
                                                                          • Changing Home Page
                                                                          • Enabling Schema Caching
                                                                          • Disabling Debugging Mode
                                                                          • Deploying the Application
                                                                            • Future Enhancements
                                                                              • Using a Theme
                                                                              • Internationalization
                                                                              • Improving Performance with Cache
                                                                              • Adding New Features
Page 44: Yii Blog 1.1.0 Demo Tutor

43 Managing Comments 37

In the above code we display the flash message if it is available If not we displaythe comment input form by rendering the partial view wwwrootblogprotectedviews

comment formphp

43 Managing Comments

Comment management includes updating deleting and approving comments These op-erations are implemented as actions in the CommentController class

431 Updating and Deleting Comments

The code generated by yiic for updating and deleting comments remains largely un-changed

432 Approving Comments

When comments are newly created they are in pending approval status and need to beapproved in order to be visible to guest users Approving a comment is mainly aboutchanging the status column of the comment

We create an actionApprove() method in CommentController as follows

public function actionApprove()

if(Yiiapp()-gtrequest-gtisPostRequest)

$comment=$this-gtloadModel()

$comment-gtapprove()

$this-gtredirect(array(rsquoindexrsquo))

else

throw new CHttpException(400rsquoInvalid requestrsquo)

In the above when the approve action is invoked via a POST request we call the approve()

method defined in the Comment model to change the status We then redirect the userbrowser to the page displaying the post that this comment belongs to

We also modify the actionIndex() method of Comment to show all comments We wouldlike to see comments pending approved to show up first

public function actionIndex()

38 4 Comment Management

$dataProvider=new CActiveDataProvider(rsquoCommentrsquo array(

rsquocriteriarsquo=gtarray(

rsquowithrsquo=gtrsquopostrsquo

rsquoorderrsquo=gtrsquotstatus tcreate time DESCrsquo

)

))

$this-gtrender(rsquoindexrsquoarray(

rsquodataProviderrsquo=gt$dataProvider

))

Notice that in the above code because both tbl post and tbl comment have columns statusand create time we need to disambiguate the corresponding column reference by prefixingthem with table alias names As described in the guide the alias for the primary table ina relational query is always t Therefore we are prefixing t to the status and create time

columns in the above code

Like the post index view the index view for CommentController uses [CListView] to displaythe comment list which in turn uses the partial view wwwrootblogprotectedviews

comment viewphp to display the detail of each individual comment We will not go intodetails here Interested readers may refer to the corresponding file in the blog demowwwrootyiidemosblogprotectedviewscomment viewphp

Chapter 5

Portlets

51 Customizing Page Layout

Features like rdquothe most recent commentsrdquo rdquotag cloudrdquo are better to be implemented inportlets A portlet is a pluggable user interface component that renders a fragment ofHTML code In this section we describe how to set up the portlet architecture for ourblog application

Before we go into details about developing portlets we first need to adjust our page layoutso that the portlets can be placed in a side bar

A page layout determines how to share common sections (eg header footer) amongdifferent pages and how to embed contents that are specific for different pages In Yiia page layout is represented as a view under the protectedviewslayouts folder Whenwe call the render() method in a controller the rendering result will be automaticallyembedded into this layout view before it is displayed to end users The default layoutview is named as main which means the wwwrootblogprotectedviewslayoutsmain

php file in our blog application

We could directly modify the main layout to add a side bar However doing so wouldmake some irrelevant pages such as about contact pages to have a side bar which is notalways desirable For this reason we need different layouts for different pages

We thus create two different layouts column1 and column2 The former displays the pagein a single column while the latter displays two columns in the page one of which is theside bar We will use column2 layout for the post and comment pages and column1 forthe rest of the pages Because both column1 and column2 still share the same header andfooter sections we let them to share the main layout

Based on the above description we create the column1 layout as follows

ltphp $this-gtbeginContent(rsquolayoutsmainrsquo) gt

ltdiv class=containergt

40 5 Portlets

ltdiv id=contentgt

ltphp echo $content gt

ltdivgtlt-- content --gt

ltdivgt

ltphp $this-gtendContent() gt

and the column2 layout as follows

ltphp $this-gtbeginContent(rsquolayoutsmainrsquo) gt

ltdiv class=containergt

ltdiv class=span-18gt

ltdiv id=contentgt

ltphp echo $content gt

ltdivgtlt-- content --gt

ltdivgt

ltdiv class=span-6 lastgt

ltdiv id=sidebargt

portlets to be placed here

ltdivgtlt-- sidebar --gt

ltdivgt

ltdivgt

ltphp $this-gtendContent() gt

As we can see both column1 and column2 are enclosed in a pair of beginContent andendContent calls They essentially use the specified view layoutsmain to decorate theenclosed content In all these layout views the variable $content refers to the dynamiccontent to be embedded into the layout

Besides customizing the layout view file we also need to adjust the CSS file wwwroot

blogcssmaincss so that the overall appearance would look like what we see in the blogdemo We will not go into details here

Info The skeleton application generated by the yiic tool uses the BluePrint CSSframework It provides a nice font reset and a robust grid system For more detailson how to use this framework please refer to its documentation

After creating the new layouts we need to set the layout property of controllers to makeuse of them So far we have three controllers SiteController PostController andCommentController We set SiteController to use column1 as the default layout whilethe rest two of the controllers use column2 for example

class PostController extends Controller

52 Creating User Menu Portlet 41

public $layout=rsquocolumn2rsquo

Tip If some view of the controller does not use the same layout as declaredby the layout property it may customize it in the view by a simple assignment$this-gtlayout=rsquonewlayoutrsquo

52 Creating User Menu Portlet

Based on the requirements analysis we need three different portlets the rdquouser menurdquoportlet the rdquotag cloudrdquo portlet and the rdquorecent commentsrdquo portlet We will develop theseportlets by extending the [CPortlet] widget provided by Yii

In this section we will develop our first concrete portlet - the user menu portlet whichdisplays a list of menu items that are only available to authenticated users The menucontains four items

bull Approve Comments a hyperlink that leads to a list of comments pending approval

bull Create New Post a hyperlink that leads to the post creation page

bull Manage Posts a hyperlink that leads to the post management page

bull Logout a link button that would log out the current user

521 Creating UserMenu Class

We create the UserMenu class to represent the logic part of the user menu portlet Theclass is saved in the file wwwrootblogprotectedcomponentsUserMenuphp which has thefollowing content

Yiiimport(rsquoziiwidgetsCPortletrsquo)

class UserMenu extends CPortlet

public function init()

$this-gttitle=CHtmlencode(Yiiapp()-gtuser-gtname)

parentinit()

42 5 Portlets

protected function renderContent()

$this-gtrender(rsquouserMenursquo)

The UserMenu class extends from the CPortlet class that we created previously It overridesboth the init() method and the renderContent() method of CPortlet The former setsthe portlet title to be the name of the current user the latter generates the portlet bodycontent by rendering a view named userMenu

Tip Notice that we have to explicitly include the CPortlet class by calling Yii

import() before we refer to it the first time This is because CPortlet is part of thezii project ndash the official extension library for Yii For performance considerationclasses in this project are not listed as core classes Therefore we have to import itbefore we use it the first time

522 Creating userMenu View

Next we create the userMenu view which is saved in the file wwwrootblogprotected

componentsviewsuserMenuphp

ltulgt

ltligtltphp echo CHtmllink(rsquoCreate New Postrsquoarray(rsquopostcreatersquo)) gtltligt

ltligtltphp echo CHtmllink(rsquoManage Postsrsquoarray(rsquopostadminrsquo)) gtltligt

ltligtltphp echo CHtmllink(rsquoApprove Commentsrsquoarray(rsquocommentindexrsquo))

rsquo (rsquo Commentmodel()-gtpendingCommentCount rsquo)rsquo gtltligt

ltligtltphp echo CHtmllink(rsquoLogoutrsquoarray(rsquositelogoutrsquo)) gtltligt

ltulgt

Info By default view files for a widget should be placed under the views sub-directory of the directory containing the widget class file The file name must bethe same as the view name

523 Using UserMenu Portlet

It is time for us to make use of our newly completed UserMenu portlet We modify thelayout view file wwwrootblogprotectedviewslayoutscolumn2php as follows

53 Creating Tag Cloud Portlet 43

ltdiv id=sidebargt

ltphp if(Yiiapp()-gtuser-gtisGuest) $this-gtwidget(rsquoUserMenursquo) gt

ltdivgt

In the above we call the widget() method to generate and execute an instance of theUserMenu class Because the portlet should only be displayed to authenticated users weonly call widget() when the isGuest property of the current user is false (meaning theuser is authenticated)

524 Testing UserMenu Portlet

Letrsquos test what we have so far

1 Open a browser window and enter the URL httpwwwexamplecomblogindex

php Verify that there is nothing displayed in the side bar section of the page

2 Click on the Login hyperlink and fill out the login form to login If successful verifythat the UserMenu portlet appears in the side bar and the portlet has the usernameas its title

3 Click on the rsquoLogoutrsquo hyperlink in the UserMenu portlet Verify that the logout actionis successful and the UserMenu portlet disappears

525 Summary

What we have created is a portlet that is highly reusable We can easily reuse it in adifferent project with little or no modification Moreover the design of this portlet followsclosely the philosophy that logic and presentation should be separated While we did notpoint this out in the previous sections such practice is used nearly everywhere in a typicalYii application

53 Creating Tag Cloud Portlet

Tag cloud displays a list of post tags with visual decorations hinting the popularity of eachindividual tag

531 Creating TagCloud Class

We create the TagCloud class in the file wwwrootblogprotectedcomponentsTagCloud

php The file has the following content

44 5 Portlets

Yiiimport(rsquoziiwidgetsCPortletrsquo)

class TagCloud extends CPortlet

public $title=rsquoTagsrsquo

public $maxTags=20

protected function renderContent()

$tags=Tagmodel()-gtfindTagWeights($this-gtmaxTags)

foreach($tags as $tag=gt$weight)

$link=CHtmllink(CHtmlencode($tag) array(rsquopostindexrsquorsquotagrsquo=gt$tag))

echo CHtmltag(rsquospanrsquo array(

rsquoclassrsquo=gtrsquotagrsquo

rsquostylersquo=gtfont-size$weightpt) $link)n

Unlike the UserMenu portlet the TagCloud portlet does not use a view Instead its presen-tation is done in the renderContent() method This is because the presentation does notcontain much HTML tags

We display each tag as a hyperlink to the post index page with the corresponding tagparameter The font size of each tag link is adjusted according to their relative weightamong other tags If a tag has higher frequency value than the other it will have a biggerfont size

532 Using TagCloud Portlet

Usage of the TagCloud portlet is very simple We modify the layout file wwwrootblog

protectedviewslayoutsmainphp as follows

ltdiv id=sidebargt

ltphp if(Yiiapp()-gtuser-gtisGuest) $this-gtwidget(rsquoUserMenursquo) gt

ltphp $this-gtwidget(rsquoTagCloudrsquo array(

rsquomaxTagsrsquo=gtYiiapp()-gtparams[rsquotagCloudCountrsquo]

)) gt

ltdivgt

54 Creating Recent Comments Portlet 45

54 Creating Recent Comments Portlet

In this section we create the last portlet that displays a list of comments recently pub-lished

541 Creating RecentComments Class

We create the RecentComments class in the file wwwrootblogprotectedcomponentsRecentCommentsphp The file has the following content

Yiiimport(rsquoziiwidgetsCPortletrsquo)

class RecentComments extends CPortlet

public $title=rsquoRecent Commentsrsquo

public $maxComments=10

public function getRecentComments()

return Commentmodel()-gtfindRecentComments($this-gtmaxComments)

protected function renderContent()

$this-gtrender(rsquorecentCommentsrsquo)

In the above we invoke the findRecentComments method which is defined in the Comment

class as follows

class Comment extends CActiveRecord

public function findRecentComments($limit=10)

return $this-gtwith(rsquopostrsquo)-gtfindAll(array(

rsquoconditionrsquo=gtrsquotstatus=rsquoselfSTATUS APPROVED

rsquoorderrsquo=gtrsquotcreate time DESCrsquo

rsquolimitrsquo=gt$limit

))

46 5 Portlets

542 Creating recentComments View

The recentComments view is saved in the file wwwrootblogprotectedcomponentsviews

recentCommentsphp It simply displays every comment returned by the RecentComments

getRecentComments() method

543 Using RecentComments Portlet

We modify the layout file wwwrootblogprotectedviewslayoutsmainphp to embedthis last portlet

ltdiv id=sidebargt

ltphp if(Yiiapp()-gtuser-gtisGuest) $this-gtwidget(rsquoUserMenursquo) gt

ltphp $this-gtwidget(rsquoTagCloudrsquo array(

rsquomaxTagsrsquo=gtYiiapp()-gtparams[rsquotagCloudCountrsquo]

)) gt

ltphp $this-gtwidget(rsquoRecentCommentsrsquo array(

rsquomaxCommentsrsquo=gtYiiapp()-gtparams[rsquorecentCommentCountrsquo]

)) gt

ltdivgt

Chapter 6

Final Work

61 Beautifying URLs

The URLs linking various pages of our blog application currently look ugly For examplethe URL for the page showing a post looks like the following

indexphpr=postshowampid=1

In this section we describe how to beautify these URLs and make them SEO-friendlyOur goal is to be able to use the following URLs in the application

1 indexphppostsyii leads to the page showing a list of posts with tag yii

2 indexphppost2A+Test+Post leads to the page showing the detail of the postwith ID 2 whose title is A Test Post

3 indexphppostupdate1 leads to the page that allows updating the post with ID1

Note that in the second URL format we include the post title in the URL This is mainlyto make the URL SEO friendly It is said that search engines may also respect the wordsfound in a URL when it is being indexed

To achieve our goal we modify the application configuration as follows

return array(

rsquocomponentsrsquo=gtarray(

rsquourlManagerrsquo=gtarray(

rsquourlFormatrsquo=gtrsquopathrsquo

rsquorulesrsquo=gtarray(

48 6 Final Work

rsquopostltidd+gtlttitlegtrsquo=gtrsquopostviewrsquorsquopostslttaggtrsquo=gtrsquopostindexrsquo

)

)

)

)

In the above we configure the urlManager component by setting its urlFormat propertyto be path and adding a set of rules

The rules are used by urlManager to parse and create the URLs in the desired formatFor example the first rule says that if a URL indexphppostsyii is requested theurlManager component should be responsible to dispatch the request to the route post

index and generate a tag GET parameter with the value yii On the other hand whencreating a URL with the route postindex and parameter tag the urlManager componentwill also use this rule to generate the desired URL indexphppostsyii For this reasonwe say that urlManager is a two-way URL manager

The urlManager component can further beautify our URLs such as hiding indexphp inthe URLs appending suffix like html to the URLs We can obtain these features easilyby configuring various properties of urlManager in the application configuration For moredetails please refer to the Guide

62 Logging Errors

A production Web application often needs sophisticated logging for various events In ourblog application we would like to log the errors occurring when it is being used Sucherrors could be programming mistakes or usersrsquo misuse of the system Logging these errorswill help us to improve the blog application

We enable the error logging by modifying the application configuration as follows

return array(

rsquopreloadrsquo=gtarray(rsquologrsquo)

rsquocomponentsrsquo=gtarray(

rsquologrsquo=gtarray(

rsquoclassrsquo=gtrsquoCLogRouterrsquo

rsquoroutesrsquo=gtarray(

array(

rsquoclassrsquo=gtrsquoCFileLogRoutersquo

rsquolevelsrsquo=gtrsquoerror warningrsquo

63 Final Tune-up and Deployment 49

)

)

)

)

)

With the above configuration if an error or warning occurs detailed information will belogged and saved in a file located under the directory wwwrootblogprotectedruntime

The log component offers more advanced features such as sending log messages to a listof email addresses displaying log messages in JavaScript console window etc For moredetails please refer to the Guide

63 Final Tune-up and Deployment

We are close to finish our blog application Before deployment we would like to do sometune-ups

631 Changing Home Page

We change to use the post list page as the home page We modify the application config-uration as follows

return array(

rsquodefaultControllerrsquo=gtrsquopostrsquo

)

Tip Because PostController already declares list to be its default action whenwe access the home page of the application we will see the result generated by thelist action of the post controller

632 Enabling Schema Caching

Because ActiveRecord relies on the metadata about tables to determine the column infor-mation it takes time to read the metadata and analyze it This may not be a problemduring development stage but for an application running in production mode it is a totalwaste of time if the database schema does not change Therefore we should enable theschema caching by modifying the application configuration as follows

50 6 Final Work

return array(

rsquocomponentsrsquo=gtarray(

rsquocachersquo=gtarray(

rsquoclassrsquo=gtrsquoCDbCachersquo

)

rsquodbrsquo=gtarray(

rsquoclassrsquo=gtrsquosystemdbCDbConnectionrsquo

rsquoconnectionStringrsquo=gtrsquosqlitewwwrootblogprotecteddatablogdbrsquo

rsquoschemaCachingDurationrsquo=gt3600

)

)

)

In the above we first add a cache component which uses a default SQLite database as thecaching storage If our server is equipped with other caching extensions such as APCwe could change to use them as well We also modify the db component by setting itsschemaCachingDuration property to be 3600 which means the parsed database schemadata can remain valid in cache for 3600 seconds

633 Disabling Debugging Mode

We modify the entry script file wwwrootblogindexphp by removing the line definingthe constant YII DEBUG This constant is useful during development stage because it allowsYii to display more debugging information when an error occurs However when theapplication is running in production mode displaying debugging information is not agood idea because it may contain sensitive information such as where the script file islocated and the content in the file etc

634 Deploying the Application

The final deployment process manly involves copying the directory wwwrootblog to thetarget directory The following checklist shows every needed step

1 Install Yii in the target place if it is not available

2 Copy the entire directory wwwrootblog to the target place

3 Edit the entry script file indexphp by pointing the $yii variable to the new Yiibootstrap file

4 Edit the file protectedyiicphp by setting the $yiic variable to be the new Yiiyiicphp file

64 Future Enhancements 51

5 Change the permission of the directories assets and protectedruntime so that theyare writable by the Web server process

64 Future Enhancements

641 Using a Theme

Without writing any code our blog application is already themeable To use a themewe mainly need to develop the theme by writing customized view files in the theme Forexample to use a theme named classic that uses a different page layout we would createa layout view file wwwrootblogthemesclassicviewslayoutsmainphp We also needto change the application configuration to indicate our choice of the classic theme

return array(

rsquothemersquo=gtrsquoclassicrsquo

)

642 Internationalization

We may also internationalize our blog application so that its pages can be displayed indifferent languages This mainly involves efforts in two aspects

First we may create view files in different languages For example for the list pageof PostController we can create a view file wwwrootblogprotectedviewspostzh cn

listphp When the application is configured to use simplified Chinese (the language codeis zh cn) Yii will automatically use this new view file instead of the original one

Second we may create message translations for those messages generated by code Themessage translations should be saved as files under the directory wwwrootblogprotected

messages We also need to modify the code where we use text strings by enclosing themin the method call Yiit()

For more details about internationalization please refer to the Guide

643 Improving Performance with Cache

While the Yii framework itself is very efficient it is not necessarily true that an applicationwritten in Yii efficient There are several places in our blog application that we can improvethe performance For example the tag clould portlet could be one of the performancebottlenecks because it involves complex database query and PHP logic

52 6 Final Work

We can make use of the sophisticated caching feature provided by Yii to improve theperformance One of the most useful components in Yii is COutputCache which caches afragment of page display so that the underlying code generating the fragment does not needto be executed for every request For example in the layout file wwwrootblogprotected

viewslayoutsmainphp we can enclose the tag cloud portlet with COutputCache

ltphp if($this-gtbeginCache(rsquotagCloudrsquo array(rsquodurationrsquo=gt3600))) gt

ltphp $this-gtwidget(rsquoTagCloudrsquo array(

rsquomaxTagsrsquo=gtYiiapp()-gtparams[rsquotagCloudCountrsquo]

)) gt

ltphp $this-gtendCache() gt

With the above code the tag cloud display will be served from cache instead of beinggenerated on-the-fly for every request The cached content will remain valid in cache for3600 seconds

644 Adding New Features

Our blog application only has very basic functionalities To become a complete blogsystem more features are needed for example calendar portlet email notifications postcategorization archived post portlet and so on We will leave the implementation of thesefeatures to interested readers

  • Contents
  • License
  • Getting Started
    • Building a Blog System using Yii
    • Testdriving with Yii
      • Installing Yii
      • Creating Skeleton Application
      • Application Workflow
        • Requirements Analysis
        • Overall Design
          • Initial Prototyping
            • Setting Up Database
              • Creating Database
              • Establishing Database Connection
                • Scaffolding
                • Authenticating User
                • Summary
                  • Post Management
                    • Customizing Post Model
                      • Customizing rules() Method
                      • Customizing relations() Method
                      • Representing Status in Text
                        • Creating and Updating Posts
                          • Customizing Access Control
                          • Customizing create and update Operations
                            • Displaying Posts
                              • Customizing view Operation
                              • Customizing index Operation
                                • Managing Posts
                                  • Listing Posts in Tabular View
                                  • Deleting Posts
                                      • Comment Management
                                        • Customizing Comment Model
                                          • Customizing rules() Method
                                          • Customizing attributeLabels() Method
                                          • Customizing Saving Process
                                            • Creating and Displaying Comments
                                              • Displaying Comments
                                              • Creating Comments
                                                • Managing Comments
                                                  • Updating and Deleting Comments
                                                  • Approving Comments
                                                      • Portlets
                                                        • Customizing Page Layout
                                                        • Creating User Menu Portlet
                                                          • Creating UserMenu Class
                                                          • Creating userMenu View
                                                          • Using UserMenu Portlet
                                                          • Testing UserMenu Portlet
                                                          • Summary
                                                            • Creating Tag Cloud Portlet
                                                              • Creating TagCloud Class
                                                              • Using TagCloud Portlet
                                                                • Creating Recent Comments Portlet
                                                                  • Creating RecentComments Class
                                                                  • Creating recentComments View
                                                                  • Using RecentComments Portlet
                                                                      • Final Work
                                                                        • Beautifying URLs
                                                                        • Logging Errors
                                                                        • Final Tune-up and Deployment
                                                                          • Changing Home Page
                                                                          • Enabling Schema Caching
                                                                          • Disabling Debugging Mode
                                                                          • Deploying the Application
                                                                            • Future Enhancements
                                                                              • Using a Theme
                                                                              • Internationalization
                                                                              • Improving Performance with Cache
                                                                              • Adding New Features
Page 45: Yii Blog 1.1.0 Demo Tutor

38 4 Comment Management

$dataProvider=new CActiveDataProvider(rsquoCommentrsquo array(

rsquocriteriarsquo=gtarray(

rsquowithrsquo=gtrsquopostrsquo

rsquoorderrsquo=gtrsquotstatus tcreate time DESCrsquo

)

))

$this-gtrender(rsquoindexrsquoarray(

rsquodataProviderrsquo=gt$dataProvider

))

Notice that in the above code because both tbl post and tbl comment have columns statusand create time we need to disambiguate the corresponding column reference by prefixingthem with table alias names As described in the guide the alias for the primary table ina relational query is always t Therefore we are prefixing t to the status and create time

columns in the above code

Like the post index view the index view for CommentController uses [CListView] to displaythe comment list which in turn uses the partial view wwwrootblogprotectedviews

comment viewphp to display the detail of each individual comment We will not go intodetails here Interested readers may refer to the corresponding file in the blog demowwwrootyiidemosblogprotectedviewscomment viewphp

Chapter 5

Portlets

51 Customizing Page Layout

Features like rdquothe most recent commentsrdquo rdquotag cloudrdquo are better to be implemented inportlets A portlet is a pluggable user interface component that renders a fragment ofHTML code In this section we describe how to set up the portlet architecture for ourblog application

Before we go into details about developing portlets we first need to adjust our page layoutso that the portlets can be placed in a side bar

A page layout determines how to share common sections (eg header footer) amongdifferent pages and how to embed contents that are specific for different pages In Yiia page layout is represented as a view under the protectedviewslayouts folder Whenwe call the render() method in a controller the rendering result will be automaticallyembedded into this layout view before it is displayed to end users The default layoutview is named as main which means the wwwrootblogprotectedviewslayoutsmain

php file in our blog application

We could directly modify the main layout to add a side bar However doing so wouldmake some irrelevant pages such as about contact pages to have a side bar which is notalways desirable For this reason we need different layouts for different pages

We thus create two different layouts column1 and column2 The former displays the pagein a single column while the latter displays two columns in the page one of which is theside bar We will use column2 layout for the post and comment pages and column1 forthe rest of the pages Because both column1 and column2 still share the same header andfooter sections we let them to share the main layout

Based on the above description we create the column1 layout as follows

ltphp $this-gtbeginContent(rsquolayoutsmainrsquo) gt

ltdiv class=containergt

40 5 Portlets

ltdiv id=contentgt

ltphp echo $content gt

ltdivgtlt-- content --gt

ltdivgt

ltphp $this-gtendContent() gt

and the column2 layout as follows

ltphp $this-gtbeginContent(rsquolayoutsmainrsquo) gt

ltdiv class=containergt

ltdiv class=span-18gt

ltdiv id=contentgt

ltphp echo $content gt

ltdivgtlt-- content --gt

ltdivgt

ltdiv class=span-6 lastgt

ltdiv id=sidebargt

portlets to be placed here

ltdivgtlt-- sidebar --gt

ltdivgt

ltdivgt

ltphp $this-gtendContent() gt

As we can see both column1 and column2 are enclosed in a pair of beginContent andendContent calls They essentially use the specified view layoutsmain to decorate theenclosed content In all these layout views the variable $content refers to the dynamiccontent to be embedded into the layout

Besides customizing the layout view file we also need to adjust the CSS file wwwroot

blogcssmaincss so that the overall appearance would look like what we see in the blogdemo We will not go into details here

Info The skeleton application generated by the yiic tool uses the BluePrint CSSframework It provides a nice font reset and a robust grid system For more detailson how to use this framework please refer to its documentation

After creating the new layouts we need to set the layout property of controllers to makeuse of them So far we have three controllers SiteController PostController andCommentController We set SiteController to use column1 as the default layout whilethe rest two of the controllers use column2 for example

class PostController extends Controller

52 Creating User Menu Portlet 41

public $layout=rsquocolumn2rsquo

Tip If some view of the controller does not use the same layout as declaredby the layout property it may customize it in the view by a simple assignment$this-gtlayout=rsquonewlayoutrsquo

52 Creating User Menu Portlet

Based on the requirements analysis we need three different portlets the rdquouser menurdquoportlet the rdquotag cloudrdquo portlet and the rdquorecent commentsrdquo portlet We will develop theseportlets by extending the [CPortlet] widget provided by Yii

In this section we will develop our first concrete portlet - the user menu portlet whichdisplays a list of menu items that are only available to authenticated users The menucontains four items

bull Approve Comments a hyperlink that leads to a list of comments pending approval

bull Create New Post a hyperlink that leads to the post creation page

bull Manage Posts a hyperlink that leads to the post management page

bull Logout a link button that would log out the current user

521 Creating UserMenu Class

We create the UserMenu class to represent the logic part of the user menu portlet Theclass is saved in the file wwwrootblogprotectedcomponentsUserMenuphp which has thefollowing content

Yiiimport(rsquoziiwidgetsCPortletrsquo)

class UserMenu extends CPortlet

public function init()

$this-gttitle=CHtmlencode(Yiiapp()-gtuser-gtname)

parentinit()

42 5 Portlets

protected function renderContent()

$this-gtrender(rsquouserMenursquo)

The UserMenu class extends from the CPortlet class that we created previously It overridesboth the init() method and the renderContent() method of CPortlet The former setsthe portlet title to be the name of the current user the latter generates the portlet bodycontent by rendering a view named userMenu

Tip Notice that we have to explicitly include the CPortlet class by calling Yii

import() before we refer to it the first time This is because CPortlet is part of thezii project ndash the official extension library for Yii For performance considerationclasses in this project are not listed as core classes Therefore we have to import itbefore we use it the first time

522 Creating userMenu View

Next we create the userMenu view which is saved in the file wwwrootblogprotected

componentsviewsuserMenuphp

ltulgt

ltligtltphp echo CHtmllink(rsquoCreate New Postrsquoarray(rsquopostcreatersquo)) gtltligt

ltligtltphp echo CHtmllink(rsquoManage Postsrsquoarray(rsquopostadminrsquo)) gtltligt

ltligtltphp echo CHtmllink(rsquoApprove Commentsrsquoarray(rsquocommentindexrsquo))

rsquo (rsquo Commentmodel()-gtpendingCommentCount rsquo)rsquo gtltligt

ltligtltphp echo CHtmllink(rsquoLogoutrsquoarray(rsquositelogoutrsquo)) gtltligt

ltulgt

Info By default view files for a widget should be placed under the views sub-directory of the directory containing the widget class file The file name must bethe same as the view name

523 Using UserMenu Portlet

It is time for us to make use of our newly completed UserMenu portlet We modify thelayout view file wwwrootblogprotectedviewslayoutscolumn2php as follows

53 Creating Tag Cloud Portlet 43

ltdiv id=sidebargt

ltphp if(Yiiapp()-gtuser-gtisGuest) $this-gtwidget(rsquoUserMenursquo) gt

ltdivgt

In the above we call the widget() method to generate and execute an instance of theUserMenu class Because the portlet should only be displayed to authenticated users weonly call widget() when the isGuest property of the current user is false (meaning theuser is authenticated)

524 Testing UserMenu Portlet

Letrsquos test what we have so far

1 Open a browser window and enter the URL httpwwwexamplecomblogindex

php Verify that there is nothing displayed in the side bar section of the page

2 Click on the Login hyperlink and fill out the login form to login If successful verifythat the UserMenu portlet appears in the side bar and the portlet has the usernameas its title

3 Click on the rsquoLogoutrsquo hyperlink in the UserMenu portlet Verify that the logout actionis successful and the UserMenu portlet disappears

525 Summary

What we have created is a portlet that is highly reusable We can easily reuse it in adifferent project with little or no modification Moreover the design of this portlet followsclosely the philosophy that logic and presentation should be separated While we did notpoint this out in the previous sections such practice is used nearly everywhere in a typicalYii application

53 Creating Tag Cloud Portlet

Tag cloud displays a list of post tags with visual decorations hinting the popularity of eachindividual tag

531 Creating TagCloud Class

We create the TagCloud class in the file wwwrootblogprotectedcomponentsTagCloud

php The file has the following content

44 5 Portlets

Yiiimport(rsquoziiwidgetsCPortletrsquo)

class TagCloud extends CPortlet

public $title=rsquoTagsrsquo

public $maxTags=20

protected function renderContent()

$tags=Tagmodel()-gtfindTagWeights($this-gtmaxTags)

foreach($tags as $tag=gt$weight)

$link=CHtmllink(CHtmlencode($tag) array(rsquopostindexrsquorsquotagrsquo=gt$tag))

echo CHtmltag(rsquospanrsquo array(

rsquoclassrsquo=gtrsquotagrsquo

rsquostylersquo=gtfont-size$weightpt) $link)n

Unlike the UserMenu portlet the TagCloud portlet does not use a view Instead its presen-tation is done in the renderContent() method This is because the presentation does notcontain much HTML tags

We display each tag as a hyperlink to the post index page with the corresponding tagparameter The font size of each tag link is adjusted according to their relative weightamong other tags If a tag has higher frequency value than the other it will have a biggerfont size

532 Using TagCloud Portlet

Usage of the TagCloud portlet is very simple We modify the layout file wwwrootblog

protectedviewslayoutsmainphp as follows

ltdiv id=sidebargt

ltphp if(Yiiapp()-gtuser-gtisGuest) $this-gtwidget(rsquoUserMenursquo) gt

ltphp $this-gtwidget(rsquoTagCloudrsquo array(

rsquomaxTagsrsquo=gtYiiapp()-gtparams[rsquotagCloudCountrsquo]

)) gt

ltdivgt

54 Creating Recent Comments Portlet 45

54 Creating Recent Comments Portlet

In this section we create the last portlet that displays a list of comments recently pub-lished

541 Creating RecentComments Class

We create the RecentComments class in the file wwwrootblogprotectedcomponentsRecentCommentsphp The file has the following content

Yiiimport(rsquoziiwidgetsCPortletrsquo)

class RecentComments extends CPortlet

public $title=rsquoRecent Commentsrsquo

public $maxComments=10

public function getRecentComments()

return Commentmodel()-gtfindRecentComments($this-gtmaxComments)

protected function renderContent()

$this-gtrender(rsquorecentCommentsrsquo)

In the above we invoke the findRecentComments method which is defined in the Comment

class as follows

class Comment extends CActiveRecord

public function findRecentComments($limit=10)

return $this-gtwith(rsquopostrsquo)-gtfindAll(array(

rsquoconditionrsquo=gtrsquotstatus=rsquoselfSTATUS APPROVED

rsquoorderrsquo=gtrsquotcreate time DESCrsquo

rsquolimitrsquo=gt$limit

))

46 5 Portlets

542 Creating recentComments View

The recentComments view is saved in the file wwwrootblogprotectedcomponentsviews

recentCommentsphp It simply displays every comment returned by the RecentComments

getRecentComments() method

543 Using RecentComments Portlet

We modify the layout file wwwrootblogprotectedviewslayoutsmainphp to embedthis last portlet

ltdiv id=sidebargt

ltphp if(Yiiapp()-gtuser-gtisGuest) $this-gtwidget(rsquoUserMenursquo) gt

ltphp $this-gtwidget(rsquoTagCloudrsquo array(

rsquomaxTagsrsquo=gtYiiapp()-gtparams[rsquotagCloudCountrsquo]

)) gt

ltphp $this-gtwidget(rsquoRecentCommentsrsquo array(

rsquomaxCommentsrsquo=gtYiiapp()-gtparams[rsquorecentCommentCountrsquo]

)) gt

ltdivgt

Chapter 6

Final Work

61 Beautifying URLs

The URLs linking various pages of our blog application currently look ugly For examplethe URL for the page showing a post looks like the following

indexphpr=postshowampid=1

In this section we describe how to beautify these URLs and make them SEO-friendlyOur goal is to be able to use the following URLs in the application

1 indexphppostsyii leads to the page showing a list of posts with tag yii

2 indexphppost2A+Test+Post leads to the page showing the detail of the postwith ID 2 whose title is A Test Post

3 indexphppostupdate1 leads to the page that allows updating the post with ID1

Note that in the second URL format we include the post title in the URL This is mainlyto make the URL SEO friendly It is said that search engines may also respect the wordsfound in a URL when it is being indexed

To achieve our goal we modify the application configuration as follows

return array(

rsquocomponentsrsquo=gtarray(

rsquourlManagerrsquo=gtarray(

rsquourlFormatrsquo=gtrsquopathrsquo

rsquorulesrsquo=gtarray(

48 6 Final Work

rsquopostltidd+gtlttitlegtrsquo=gtrsquopostviewrsquorsquopostslttaggtrsquo=gtrsquopostindexrsquo

)

)

)

)

In the above we configure the urlManager component by setting its urlFormat propertyto be path and adding a set of rules

The rules are used by urlManager to parse and create the URLs in the desired formatFor example the first rule says that if a URL indexphppostsyii is requested theurlManager component should be responsible to dispatch the request to the route post

index and generate a tag GET parameter with the value yii On the other hand whencreating a URL with the route postindex and parameter tag the urlManager componentwill also use this rule to generate the desired URL indexphppostsyii For this reasonwe say that urlManager is a two-way URL manager

The urlManager component can further beautify our URLs such as hiding indexphp inthe URLs appending suffix like html to the URLs We can obtain these features easilyby configuring various properties of urlManager in the application configuration For moredetails please refer to the Guide

62 Logging Errors

A production Web application often needs sophisticated logging for various events In ourblog application we would like to log the errors occurring when it is being used Sucherrors could be programming mistakes or usersrsquo misuse of the system Logging these errorswill help us to improve the blog application

We enable the error logging by modifying the application configuration as follows

return array(

rsquopreloadrsquo=gtarray(rsquologrsquo)

rsquocomponentsrsquo=gtarray(

rsquologrsquo=gtarray(

rsquoclassrsquo=gtrsquoCLogRouterrsquo

rsquoroutesrsquo=gtarray(

array(

rsquoclassrsquo=gtrsquoCFileLogRoutersquo

rsquolevelsrsquo=gtrsquoerror warningrsquo

63 Final Tune-up and Deployment 49

)

)

)

)

)

With the above configuration if an error or warning occurs detailed information will belogged and saved in a file located under the directory wwwrootblogprotectedruntime

The log component offers more advanced features such as sending log messages to a listof email addresses displaying log messages in JavaScript console window etc For moredetails please refer to the Guide

63 Final Tune-up and Deployment

We are close to finish our blog application Before deployment we would like to do sometune-ups

631 Changing Home Page

We change to use the post list page as the home page We modify the application config-uration as follows

return array(

rsquodefaultControllerrsquo=gtrsquopostrsquo

)

Tip Because PostController already declares list to be its default action whenwe access the home page of the application we will see the result generated by thelist action of the post controller

632 Enabling Schema Caching

Because ActiveRecord relies on the metadata about tables to determine the column infor-mation it takes time to read the metadata and analyze it This may not be a problemduring development stage but for an application running in production mode it is a totalwaste of time if the database schema does not change Therefore we should enable theschema caching by modifying the application configuration as follows

50 6 Final Work

return array(

rsquocomponentsrsquo=gtarray(

rsquocachersquo=gtarray(

rsquoclassrsquo=gtrsquoCDbCachersquo

)

rsquodbrsquo=gtarray(

rsquoclassrsquo=gtrsquosystemdbCDbConnectionrsquo

rsquoconnectionStringrsquo=gtrsquosqlitewwwrootblogprotecteddatablogdbrsquo

rsquoschemaCachingDurationrsquo=gt3600

)

)

)

In the above we first add a cache component which uses a default SQLite database as thecaching storage If our server is equipped with other caching extensions such as APCwe could change to use them as well We also modify the db component by setting itsschemaCachingDuration property to be 3600 which means the parsed database schemadata can remain valid in cache for 3600 seconds

633 Disabling Debugging Mode

We modify the entry script file wwwrootblogindexphp by removing the line definingthe constant YII DEBUG This constant is useful during development stage because it allowsYii to display more debugging information when an error occurs However when theapplication is running in production mode displaying debugging information is not agood idea because it may contain sensitive information such as where the script file islocated and the content in the file etc

634 Deploying the Application

The final deployment process manly involves copying the directory wwwrootblog to thetarget directory The following checklist shows every needed step

1 Install Yii in the target place if it is not available

2 Copy the entire directory wwwrootblog to the target place

3 Edit the entry script file indexphp by pointing the $yii variable to the new Yiibootstrap file

4 Edit the file protectedyiicphp by setting the $yiic variable to be the new Yiiyiicphp file

64 Future Enhancements 51

5 Change the permission of the directories assets and protectedruntime so that theyare writable by the Web server process

64 Future Enhancements

641 Using a Theme

Without writing any code our blog application is already themeable To use a themewe mainly need to develop the theme by writing customized view files in the theme Forexample to use a theme named classic that uses a different page layout we would createa layout view file wwwrootblogthemesclassicviewslayoutsmainphp We also needto change the application configuration to indicate our choice of the classic theme

return array(

rsquothemersquo=gtrsquoclassicrsquo

)

642 Internationalization

We may also internationalize our blog application so that its pages can be displayed indifferent languages This mainly involves efforts in two aspects

First we may create view files in different languages For example for the list pageof PostController we can create a view file wwwrootblogprotectedviewspostzh cn

listphp When the application is configured to use simplified Chinese (the language codeis zh cn) Yii will automatically use this new view file instead of the original one

Second we may create message translations for those messages generated by code Themessage translations should be saved as files under the directory wwwrootblogprotected

messages We also need to modify the code where we use text strings by enclosing themin the method call Yiit()

For more details about internationalization please refer to the Guide

643 Improving Performance with Cache

While the Yii framework itself is very efficient it is not necessarily true that an applicationwritten in Yii efficient There are several places in our blog application that we can improvethe performance For example the tag clould portlet could be one of the performancebottlenecks because it involves complex database query and PHP logic

52 6 Final Work

We can make use of the sophisticated caching feature provided by Yii to improve theperformance One of the most useful components in Yii is COutputCache which caches afragment of page display so that the underlying code generating the fragment does not needto be executed for every request For example in the layout file wwwrootblogprotected

viewslayoutsmainphp we can enclose the tag cloud portlet with COutputCache

ltphp if($this-gtbeginCache(rsquotagCloudrsquo array(rsquodurationrsquo=gt3600))) gt

ltphp $this-gtwidget(rsquoTagCloudrsquo array(

rsquomaxTagsrsquo=gtYiiapp()-gtparams[rsquotagCloudCountrsquo]

)) gt

ltphp $this-gtendCache() gt

With the above code the tag cloud display will be served from cache instead of beinggenerated on-the-fly for every request The cached content will remain valid in cache for3600 seconds

644 Adding New Features

Our blog application only has very basic functionalities To become a complete blogsystem more features are needed for example calendar portlet email notifications postcategorization archived post portlet and so on We will leave the implementation of thesefeatures to interested readers

  • Contents
  • License
  • Getting Started
    • Building a Blog System using Yii
    • Testdriving with Yii
      • Installing Yii
      • Creating Skeleton Application
      • Application Workflow
        • Requirements Analysis
        • Overall Design
          • Initial Prototyping
            • Setting Up Database
              • Creating Database
              • Establishing Database Connection
                • Scaffolding
                • Authenticating User
                • Summary
                  • Post Management
                    • Customizing Post Model
                      • Customizing rules() Method
                      • Customizing relations() Method
                      • Representing Status in Text
                        • Creating and Updating Posts
                          • Customizing Access Control
                          • Customizing create and update Operations
                            • Displaying Posts
                              • Customizing view Operation
                              • Customizing index Operation
                                • Managing Posts
                                  • Listing Posts in Tabular View
                                  • Deleting Posts
                                      • Comment Management
                                        • Customizing Comment Model
                                          • Customizing rules() Method
                                          • Customizing attributeLabels() Method
                                          • Customizing Saving Process
                                            • Creating and Displaying Comments
                                              • Displaying Comments
                                              • Creating Comments
                                                • Managing Comments
                                                  • Updating and Deleting Comments
                                                  • Approving Comments
                                                      • Portlets
                                                        • Customizing Page Layout
                                                        • Creating User Menu Portlet
                                                          • Creating UserMenu Class
                                                          • Creating userMenu View
                                                          • Using UserMenu Portlet
                                                          • Testing UserMenu Portlet
                                                          • Summary
                                                            • Creating Tag Cloud Portlet
                                                              • Creating TagCloud Class
                                                              • Using TagCloud Portlet
                                                                • Creating Recent Comments Portlet
                                                                  • Creating RecentComments Class
                                                                  • Creating recentComments View
                                                                  • Using RecentComments Portlet
                                                                      • Final Work
                                                                        • Beautifying URLs
                                                                        • Logging Errors
                                                                        • Final Tune-up and Deployment
                                                                          • Changing Home Page
                                                                          • Enabling Schema Caching
                                                                          • Disabling Debugging Mode
                                                                          • Deploying the Application
                                                                            • Future Enhancements
                                                                              • Using a Theme
                                                                              • Internationalization
                                                                              • Improving Performance with Cache
                                                                              • Adding New Features
Page 46: Yii Blog 1.1.0 Demo Tutor

Chapter 5

Portlets

51 Customizing Page Layout

Features like rdquothe most recent commentsrdquo rdquotag cloudrdquo are better to be implemented inportlets A portlet is a pluggable user interface component that renders a fragment ofHTML code In this section we describe how to set up the portlet architecture for ourblog application

Before we go into details about developing portlets we first need to adjust our page layoutso that the portlets can be placed in a side bar

A page layout determines how to share common sections (eg header footer) amongdifferent pages and how to embed contents that are specific for different pages In Yiia page layout is represented as a view under the protectedviewslayouts folder Whenwe call the render() method in a controller the rendering result will be automaticallyembedded into this layout view before it is displayed to end users The default layoutview is named as main which means the wwwrootblogprotectedviewslayoutsmain

php file in our blog application

We could directly modify the main layout to add a side bar However doing so wouldmake some irrelevant pages such as about contact pages to have a side bar which is notalways desirable For this reason we need different layouts for different pages

We thus create two different layouts column1 and column2 The former displays the pagein a single column while the latter displays two columns in the page one of which is theside bar We will use column2 layout for the post and comment pages and column1 forthe rest of the pages Because both column1 and column2 still share the same header andfooter sections we let them to share the main layout

Based on the above description we create the column1 layout as follows

ltphp $this-gtbeginContent(rsquolayoutsmainrsquo) gt

ltdiv class=containergt

40 5 Portlets

ltdiv id=contentgt

ltphp echo $content gt

ltdivgtlt-- content --gt

ltdivgt

ltphp $this-gtendContent() gt

and the column2 layout as follows

ltphp $this-gtbeginContent(rsquolayoutsmainrsquo) gt

ltdiv class=containergt

ltdiv class=span-18gt

ltdiv id=contentgt

ltphp echo $content gt

ltdivgtlt-- content --gt

ltdivgt

ltdiv class=span-6 lastgt

ltdiv id=sidebargt

portlets to be placed here

ltdivgtlt-- sidebar --gt

ltdivgt

ltdivgt

ltphp $this-gtendContent() gt

As we can see both column1 and column2 are enclosed in a pair of beginContent andendContent calls They essentially use the specified view layoutsmain to decorate theenclosed content In all these layout views the variable $content refers to the dynamiccontent to be embedded into the layout

Besides customizing the layout view file we also need to adjust the CSS file wwwroot

blogcssmaincss so that the overall appearance would look like what we see in the blogdemo We will not go into details here

Info The skeleton application generated by the yiic tool uses the BluePrint CSSframework It provides a nice font reset and a robust grid system For more detailson how to use this framework please refer to its documentation

After creating the new layouts we need to set the layout property of controllers to makeuse of them So far we have three controllers SiteController PostController andCommentController We set SiteController to use column1 as the default layout whilethe rest two of the controllers use column2 for example

class PostController extends Controller

52 Creating User Menu Portlet 41

public $layout=rsquocolumn2rsquo

Tip If some view of the controller does not use the same layout as declaredby the layout property it may customize it in the view by a simple assignment$this-gtlayout=rsquonewlayoutrsquo

52 Creating User Menu Portlet

Based on the requirements analysis we need three different portlets the rdquouser menurdquoportlet the rdquotag cloudrdquo portlet and the rdquorecent commentsrdquo portlet We will develop theseportlets by extending the [CPortlet] widget provided by Yii

In this section we will develop our first concrete portlet - the user menu portlet whichdisplays a list of menu items that are only available to authenticated users The menucontains four items

bull Approve Comments a hyperlink that leads to a list of comments pending approval

bull Create New Post a hyperlink that leads to the post creation page

bull Manage Posts a hyperlink that leads to the post management page

bull Logout a link button that would log out the current user

521 Creating UserMenu Class

We create the UserMenu class to represent the logic part of the user menu portlet Theclass is saved in the file wwwrootblogprotectedcomponentsUserMenuphp which has thefollowing content

Yiiimport(rsquoziiwidgetsCPortletrsquo)

class UserMenu extends CPortlet

public function init()

$this-gttitle=CHtmlencode(Yiiapp()-gtuser-gtname)

parentinit()

42 5 Portlets

protected function renderContent()

$this-gtrender(rsquouserMenursquo)

The UserMenu class extends from the CPortlet class that we created previously It overridesboth the init() method and the renderContent() method of CPortlet The former setsthe portlet title to be the name of the current user the latter generates the portlet bodycontent by rendering a view named userMenu

Tip Notice that we have to explicitly include the CPortlet class by calling Yii

import() before we refer to it the first time This is because CPortlet is part of thezii project ndash the official extension library for Yii For performance considerationclasses in this project are not listed as core classes Therefore we have to import itbefore we use it the first time

522 Creating userMenu View

Next we create the userMenu view which is saved in the file wwwrootblogprotected

componentsviewsuserMenuphp

ltulgt

ltligtltphp echo CHtmllink(rsquoCreate New Postrsquoarray(rsquopostcreatersquo)) gtltligt

ltligtltphp echo CHtmllink(rsquoManage Postsrsquoarray(rsquopostadminrsquo)) gtltligt

ltligtltphp echo CHtmllink(rsquoApprove Commentsrsquoarray(rsquocommentindexrsquo))

rsquo (rsquo Commentmodel()-gtpendingCommentCount rsquo)rsquo gtltligt

ltligtltphp echo CHtmllink(rsquoLogoutrsquoarray(rsquositelogoutrsquo)) gtltligt

ltulgt

Info By default view files for a widget should be placed under the views sub-directory of the directory containing the widget class file The file name must bethe same as the view name

523 Using UserMenu Portlet

It is time for us to make use of our newly completed UserMenu portlet We modify thelayout view file wwwrootblogprotectedviewslayoutscolumn2php as follows

53 Creating Tag Cloud Portlet 43

ltdiv id=sidebargt

ltphp if(Yiiapp()-gtuser-gtisGuest) $this-gtwidget(rsquoUserMenursquo) gt

ltdivgt

In the above we call the widget() method to generate and execute an instance of theUserMenu class Because the portlet should only be displayed to authenticated users weonly call widget() when the isGuest property of the current user is false (meaning theuser is authenticated)

524 Testing UserMenu Portlet

Letrsquos test what we have so far

1 Open a browser window and enter the URL httpwwwexamplecomblogindex

php Verify that there is nothing displayed in the side bar section of the page

2 Click on the Login hyperlink and fill out the login form to login If successful verifythat the UserMenu portlet appears in the side bar and the portlet has the usernameas its title

3 Click on the rsquoLogoutrsquo hyperlink in the UserMenu portlet Verify that the logout actionis successful and the UserMenu portlet disappears

525 Summary

What we have created is a portlet that is highly reusable We can easily reuse it in adifferent project with little or no modification Moreover the design of this portlet followsclosely the philosophy that logic and presentation should be separated While we did notpoint this out in the previous sections such practice is used nearly everywhere in a typicalYii application

53 Creating Tag Cloud Portlet

Tag cloud displays a list of post tags with visual decorations hinting the popularity of eachindividual tag

531 Creating TagCloud Class

We create the TagCloud class in the file wwwrootblogprotectedcomponentsTagCloud

php The file has the following content

44 5 Portlets

Yiiimport(rsquoziiwidgetsCPortletrsquo)

class TagCloud extends CPortlet

public $title=rsquoTagsrsquo

public $maxTags=20

protected function renderContent()

$tags=Tagmodel()-gtfindTagWeights($this-gtmaxTags)

foreach($tags as $tag=gt$weight)

$link=CHtmllink(CHtmlencode($tag) array(rsquopostindexrsquorsquotagrsquo=gt$tag))

echo CHtmltag(rsquospanrsquo array(

rsquoclassrsquo=gtrsquotagrsquo

rsquostylersquo=gtfont-size$weightpt) $link)n

Unlike the UserMenu portlet the TagCloud portlet does not use a view Instead its presen-tation is done in the renderContent() method This is because the presentation does notcontain much HTML tags

We display each tag as a hyperlink to the post index page with the corresponding tagparameter The font size of each tag link is adjusted according to their relative weightamong other tags If a tag has higher frequency value than the other it will have a biggerfont size

532 Using TagCloud Portlet

Usage of the TagCloud portlet is very simple We modify the layout file wwwrootblog

protectedviewslayoutsmainphp as follows

ltdiv id=sidebargt

ltphp if(Yiiapp()-gtuser-gtisGuest) $this-gtwidget(rsquoUserMenursquo) gt

ltphp $this-gtwidget(rsquoTagCloudrsquo array(

rsquomaxTagsrsquo=gtYiiapp()-gtparams[rsquotagCloudCountrsquo]

)) gt

ltdivgt

54 Creating Recent Comments Portlet 45

54 Creating Recent Comments Portlet

In this section we create the last portlet that displays a list of comments recently pub-lished

541 Creating RecentComments Class

We create the RecentComments class in the file wwwrootblogprotectedcomponentsRecentCommentsphp The file has the following content

Yiiimport(rsquoziiwidgetsCPortletrsquo)

class RecentComments extends CPortlet

public $title=rsquoRecent Commentsrsquo

public $maxComments=10

public function getRecentComments()

return Commentmodel()-gtfindRecentComments($this-gtmaxComments)

protected function renderContent()

$this-gtrender(rsquorecentCommentsrsquo)

In the above we invoke the findRecentComments method which is defined in the Comment

class as follows

class Comment extends CActiveRecord

public function findRecentComments($limit=10)

return $this-gtwith(rsquopostrsquo)-gtfindAll(array(

rsquoconditionrsquo=gtrsquotstatus=rsquoselfSTATUS APPROVED

rsquoorderrsquo=gtrsquotcreate time DESCrsquo

rsquolimitrsquo=gt$limit

))

46 5 Portlets

542 Creating recentComments View

The recentComments view is saved in the file wwwrootblogprotectedcomponentsviews

recentCommentsphp It simply displays every comment returned by the RecentComments

getRecentComments() method

543 Using RecentComments Portlet

We modify the layout file wwwrootblogprotectedviewslayoutsmainphp to embedthis last portlet

ltdiv id=sidebargt

ltphp if(Yiiapp()-gtuser-gtisGuest) $this-gtwidget(rsquoUserMenursquo) gt

ltphp $this-gtwidget(rsquoTagCloudrsquo array(

rsquomaxTagsrsquo=gtYiiapp()-gtparams[rsquotagCloudCountrsquo]

)) gt

ltphp $this-gtwidget(rsquoRecentCommentsrsquo array(

rsquomaxCommentsrsquo=gtYiiapp()-gtparams[rsquorecentCommentCountrsquo]

)) gt

ltdivgt

Chapter 6

Final Work

61 Beautifying URLs

The URLs linking various pages of our blog application currently look ugly For examplethe URL for the page showing a post looks like the following

indexphpr=postshowampid=1

In this section we describe how to beautify these URLs and make them SEO-friendlyOur goal is to be able to use the following URLs in the application

1 indexphppostsyii leads to the page showing a list of posts with tag yii

2 indexphppost2A+Test+Post leads to the page showing the detail of the postwith ID 2 whose title is A Test Post

3 indexphppostupdate1 leads to the page that allows updating the post with ID1

Note that in the second URL format we include the post title in the URL This is mainlyto make the URL SEO friendly It is said that search engines may also respect the wordsfound in a URL when it is being indexed

To achieve our goal we modify the application configuration as follows

return array(

rsquocomponentsrsquo=gtarray(

rsquourlManagerrsquo=gtarray(

rsquourlFormatrsquo=gtrsquopathrsquo

rsquorulesrsquo=gtarray(

48 6 Final Work

rsquopostltidd+gtlttitlegtrsquo=gtrsquopostviewrsquorsquopostslttaggtrsquo=gtrsquopostindexrsquo

)

)

)

)

In the above we configure the urlManager component by setting its urlFormat propertyto be path and adding a set of rules

The rules are used by urlManager to parse and create the URLs in the desired formatFor example the first rule says that if a URL indexphppostsyii is requested theurlManager component should be responsible to dispatch the request to the route post

index and generate a tag GET parameter with the value yii On the other hand whencreating a URL with the route postindex and parameter tag the urlManager componentwill also use this rule to generate the desired URL indexphppostsyii For this reasonwe say that urlManager is a two-way URL manager

The urlManager component can further beautify our URLs such as hiding indexphp inthe URLs appending suffix like html to the URLs We can obtain these features easilyby configuring various properties of urlManager in the application configuration For moredetails please refer to the Guide

62 Logging Errors

A production Web application often needs sophisticated logging for various events In ourblog application we would like to log the errors occurring when it is being used Sucherrors could be programming mistakes or usersrsquo misuse of the system Logging these errorswill help us to improve the blog application

We enable the error logging by modifying the application configuration as follows

return array(

rsquopreloadrsquo=gtarray(rsquologrsquo)

rsquocomponentsrsquo=gtarray(

rsquologrsquo=gtarray(

rsquoclassrsquo=gtrsquoCLogRouterrsquo

rsquoroutesrsquo=gtarray(

array(

rsquoclassrsquo=gtrsquoCFileLogRoutersquo

rsquolevelsrsquo=gtrsquoerror warningrsquo

63 Final Tune-up and Deployment 49

)

)

)

)

)

With the above configuration if an error or warning occurs detailed information will belogged and saved in a file located under the directory wwwrootblogprotectedruntime

The log component offers more advanced features such as sending log messages to a listof email addresses displaying log messages in JavaScript console window etc For moredetails please refer to the Guide

63 Final Tune-up and Deployment

We are close to finish our blog application Before deployment we would like to do sometune-ups

631 Changing Home Page

We change to use the post list page as the home page We modify the application config-uration as follows

return array(

rsquodefaultControllerrsquo=gtrsquopostrsquo

)

Tip Because PostController already declares list to be its default action whenwe access the home page of the application we will see the result generated by thelist action of the post controller

632 Enabling Schema Caching

Because ActiveRecord relies on the metadata about tables to determine the column infor-mation it takes time to read the metadata and analyze it This may not be a problemduring development stage but for an application running in production mode it is a totalwaste of time if the database schema does not change Therefore we should enable theschema caching by modifying the application configuration as follows

50 6 Final Work

return array(

rsquocomponentsrsquo=gtarray(

rsquocachersquo=gtarray(

rsquoclassrsquo=gtrsquoCDbCachersquo

)

rsquodbrsquo=gtarray(

rsquoclassrsquo=gtrsquosystemdbCDbConnectionrsquo

rsquoconnectionStringrsquo=gtrsquosqlitewwwrootblogprotecteddatablogdbrsquo

rsquoschemaCachingDurationrsquo=gt3600

)

)

)

In the above we first add a cache component which uses a default SQLite database as thecaching storage If our server is equipped with other caching extensions such as APCwe could change to use them as well We also modify the db component by setting itsschemaCachingDuration property to be 3600 which means the parsed database schemadata can remain valid in cache for 3600 seconds

633 Disabling Debugging Mode

We modify the entry script file wwwrootblogindexphp by removing the line definingthe constant YII DEBUG This constant is useful during development stage because it allowsYii to display more debugging information when an error occurs However when theapplication is running in production mode displaying debugging information is not agood idea because it may contain sensitive information such as where the script file islocated and the content in the file etc

634 Deploying the Application

The final deployment process manly involves copying the directory wwwrootblog to thetarget directory The following checklist shows every needed step

1 Install Yii in the target place if it is not available

2 Copy the entire directory wwwrootblog to the target place

3 Edit the entry script file indexphp by pointing the $yii variable to the new Yiibootstrap file

4 Edit the file protectedyiicphp by setting the $yiic variable to be the new Yiiyiicphp file

64 Future Enhancements 51

5 Change the permission of the directories assets and protectedruntime so that theyare writable by the Web server process

64 Future Enhancements

641 Using a Theme

Without writing any code our blog application is already themeable To use a themewe mainly need to develop the theme by writing customized view files in the theme Forexample to use a theme named classic that uses a different page layout we would createa layout view file wwwrootblogthemesclassicviewslayoutsmainphp We also needto change the application configuration to indicate our choice of the classic theme

return array(

rsquothemersquo=gtrsquoclassicrsquo

)

642 Internationalization

We may also internationalize our blog application so that its pages can be displayed indifferent languages This mainly involves efforts in two aspects

First we may create view files in different languages For example for the list pageof PostController we can create a view file wwwrootblogprotectedviewspostzh cn

listphp When the application is configured to use simplified Chinese (the language codeis zh cn) Yii will automatically use this new view file instead of the original one

Second we may create message translations for those messages generated by code Themessage translations should be saved as files under the directory wwwrootblogprotected

messages We also need to modify the code where we use text strings by enclosing themin the method call Yiit()

For more details about internationalization please refer to the Guide

643 Improving Performance with Cache

While the Yii framework itself is very efficient it is not necessarily true that an applicationwritten in Yii efficient There are several places in our blog application that we can improvethe performance For example the tag clould portlet could be one of the performancebottlenecks because it involves complex database query and PHP logic

52 6 Final Work

We can make use of the sophisticated caching feature provided by Yii to improve theperformance One of the most useful components in Yii is COutputCache which caches afragment of page display so that the underlying code generating the fragment does not needto be executed for every request For example in the layout file wwwrootblogprotected

viewslayoutsmainphp we can enclose the tag cloud portlet with COutputCache

ltphp if($this-gtbeginCache(rsquotagCloudrsquo array(rsquodurationrsquo=gt3600))) gt

ltphp $this-gtwidget(rsquoTagCloudrsquo array(

rsquomaxTagsrsquo=gtYiiapp()-gtparams[rsquotagCloudCountrsquo]

)) gt

ltphp $this-gtendCache() gt

With the above code the tag cloud display will be served from cache instead of beinggenerated on-the-fly for every request The cached content will remain valid in cache for3600 seconds

644 Adding New Features

Our blog application only has very basic functionalities To become a complete blogsystem more features are needed for example calendar portlet email notifications postcategorization archived post portlet and so on We will leave the implementation of thesefeatures to interested readers

  • Contents
  • License
  • Getting Started
    • Building a Blog System using Yii
    • Testdriving with Yii
      • Installing Yii
      • Creating Skeleton Application
      • Application Workflow
        • Requirements Analysis
        • Overall Design
          • Initial Prototyping
            • Setting Up Database
              • Creating Database
              • Establishing Database Connection
                • Scaffolding
                • Authenticating User
                • Summary
                  • Post Management
                    • Customizing Post Model
                      • Customizing rules() Method
                      • Customizing relations() Method
                      • Representing Status in Text
                        • Creating and Updating Posts
                          • Customizing Access Control
                          • Customizing create and update Operations
                            • Displaying Posts
                              • Customizing view Operation
                              • Customizing index Operation
                                • Managing Posts
                                  • Listing Posts in Tabular View
                                  • Deleting Posts
                                      • Comment Management
                                        • Customizing Comment Model
                                          • Customizing rules() Method
                                          • Customizing attributeLabels() Method
                                          • Customizing Saving Process
                                            • Creating and Displaying Comments
                                              • Displaying Comments
                                              • Creating Comments
                                                • Managing Comments
                                                  • Updating and Deleting Comments
                                                  • Approving Comments
                                                      • Portlets
                                                        • Customizing Page Layout
                                                        • Creating User Menu Portlet
                                                          • Creating UserMenu Class
                                                          • Creating userMenu View
                                                          • Using UserMenu Portlet
                                                          • Testing UserMenu Portlet
                                                          • Summary
                                                            • Creating Tag Cloud Portlet
                                                              • Creating TagCloud Class
                                                              • Using TagCloud Portlet
                                                                • Creating Recent Comments Portlet
                                                                  • Creating RecentComments Class
                                                                  • Creating recentComments View
                                                                  • Using RecentComments Portlet
                                                                      • Final Work
                                                                        • Beautifying URLs
                                                                        • Logging Errors
                                                                        • Final Tune-up and Deployment
                                                                          • Changing Home Page
                                                                          • Enabling Schema Caching
                                                                          • Disabling Debugging Mode
                                                                          • Deploying the Application
                                                                            • Future Enhancements
                                                                              • Using a Theme
                                                                              • Internationalization
                                                                              • Improving Performance with Cache
                                                                              • Adding New Features
Page 47: Yii Blog 1.1.0 Demo Tutor

40 5 Portlets

ltdiv id=contentgt

ltphp echo $content gt

ltdivgtlt-- content --gt

ltdivgt

ltphp $this-gtendContent() gt

and the column2 layout as follows

ltphp $this-gtbeginContent(rsquolayoutsmainrsquo) gt

ltdiv class=containergt

ltdiv class=span-18gt

ltdiv id=contentgt

ltphp echo $content gt

ltdivgtlt-- content --gt

ltdivgt

ltdiv class=span-6 lastgt

ltdiv id=sidebargt

portlets to be placed here

ltdivgtlt-- sidebar --gt

ltdivgt

ltdivgt

ltphp $this-gtendContent() gt

As we can see both column1 and column2 are enclosed in a pair of beginContent andendContent calls They essentially use the specified view layoutsmain to decorate theenclosed content In all these layout views the variable $content refers to the dynamiccontent to be embedded into the layout

Besides customizing the layout view file we also need to adjust the CSS file wwwroot

blogcssmaincss so that the overall appearance would look like what we see in the blogdemo We will not go into details here

Info The skeleton application generated by the yiic tool uses the BluePrint CSSframework It provides a nice font reset and a robust grid system For more detailson how to use this framework please refer to its documentation

After creating the new layouts we need to set the layout property of controllers to makeuse of them So far we have three controllers SiteController PostController andCommentController We set SiteController to use column1 as the default layout whilethe rest two of the controllers use column2 for example

class PostController extends Controller

52 Creating User Menu Portlet 41

public $layout=rsquocolumn2rsquo

Tip If some view of the controller does not use the same layout as declaredby the layout property it may customize it in the view by a simple assignment$this-gtlayout=rsquonewlayoutrsquo

52 Creating User Menu Portlet

Based on the requirements analysis we need three different portlets the rdquouser menurdquoportlet the rdquotag cloudrdquo portlet and the rdquorecent commentsrdquo portlet We will develop theseportlets by extending the [CPortlet] widget provided by Yii

In this section we will develop our first concrete portlet - the user menu portlet whichdisplays a list of menu items that are only available to authenticated users The menucontains four items

bull Approve Comments a hyperlink that leads to a list of comments pending approval

bull Create New Post a hyperlink that leads to the post creation page

bull Manage Posts a hyperlink that leads to the post management page

bull Logout a link button that would log out the current user

521 Creating UserMenu Class

We create the UserMenu class to represent the logic part of the user menu portlet Theclass is saved in the file wwwrootblogprotectedcomponentsUserMenuphp which has thefollowing content

Yiiimport(rsquoziiwidgetsCPortletrsquo)

class UserMenu extends CPortlet

public function init()

$this-gttitle=CHtmlencode(Yiiapp()-gtuser-gtname)

parentinit()

42 5 Portlets

protected function renderContent()

$this-gtrender(rsquouserMenursquo)

The UserMenu class extends from the CPortlet class that we created previously It overridesboth the init() method and the renderContent() method of CPortlet The former setsthe portlet title to be the name of the current user the latter generates the portlet bodycontent by rendering a view named userMenu

Tip Notice that we have to explicitly include the CPortlet class by calling Yii

import() before we refer to it the first time This is because CPortlet is part of thezii project ndash the official extension library for Yii For performance considerationclasses in this project are not listed as core classes Therefore we have to import itbefore we use it the first time

522 Creating userMenu View

Next we create the userMenu view which is saved in the file wwwrootblogprotected

componentsviewsuserMenuphp

ltulgt

ltligtltphp echo CHtmllink(rsquoCreate New Postrsquoarray(rsquopostcreatersquo)) gtltligt

ltligtltphp echo CHtmllink(rsquoManage Postsrsquoarray(rsquopostadminrsquo)) gtltligt

ltligtltphp echo CHtmllink(rsquoApprove Commentsrsquoarray(rsquocommentindexrsquo))

rsquo (rsquo Commentmodel()-gtpendingCommentCount rsquo)rsquo gtltligt

ltligtltphp echo CHtmllink(rsquoLogoutrsquoarray(rsquositelogoutrsquo)) gtltligt

ltulgt

Info By default view files for a widget should be placed under the views sub-directory of the directory containing the widget class file The file name must bethe same as the view name

523 Using UserMenu Portlet

It is time for us to make use of our newly completed UserMenu portlet We modify thelayout view file wwwrootblogprotectedviewslayoutscolumn2php as follows

53 Creating Tag Cloud Portlet 43

ltdiv id=sidebargt

ltphp if(Yiiapp()-gtuser-gtisGuest) $this-gtwidget(rsquoUserMenursquo) gt

ltdivgt

In the above we call the widget() method to generate and execute an instance of theUserMenu class Because the portlet should only be displayed to authenticated users weonly call widget() when the isGuest property of the current user is false (meaning theuser is authenticated)

524 Testing UserMenu Portlet

Letrsquos test what we have so far

1 Open a browser window and enter the URL httpwwwexamplecomblogindex

php Verify that there is nothing displayed in the side bar section of the page

2 Click on the Login hyperlink and fill out the login form to login If successful verifythat the UserMenu portlet appears in the side bar and the portlet has the usernameas its title

3 Click on the rsquoLogoutrsquo hyperlink in the UserMenu portlet Verify that the logout actionis successful and the UserMenu portlet disappears

525 Summary

What we have created is a portlet that is highly reusable We can easily reuse it in adifferent project with little or no modification Moreover the design of this portlet followsclosely the philosophy that logic and presentation should be separated While we did notpoint this out in the previous sections such practice is used nearly everywhere in a typicalYii application

53 Creating Tag Cloud Portlet

Tag cloud displays a list of post tags with visual decorations hinting the popularity of eachindividual tag

531 Creating TagCloud Class

We create the TagCloud class in the file wwwrootblogprotectedcomponentsTagCloud

php The file has the following content

44 5 Portlets

Yiiimport(rsquoziiwidgetsCPortletrsquo)

class TagCloud extends CPortlet

public $title=rsquoTagsrsquo

public $maxTags=20

protected function renderContent()

$tags=Tagmodel()-gtfindTagWeights($this-gtmaxTags)

foreach($tags as $tag=gt$weight)

$link=CHtmllink(CHtmlencode($tag) array(rsquopostindexrsquorsquotagrsquo=gt$tag))

echo CHtmltag(rsquospanrsquo array(

rsquoclassrsquo=gtrsquotagrsquo

rsquostylersquo=gtfont-size$weightpt) $link)n

Unlike the UserMenu portlet the TagCloud portlet does not use a view Instead its presen-tation is done in the renderContent() method This is because the presentation does notcontain much HTML tags

We display each tag as a hyperlink to the post index page with the corresponding tagparameter The font size of each tag link is adjusted according to their relative weightamong other tags If a tag has higher frequency value than the other it will have a biggerfont size

532 Using TagCloud Portlet

Usage of the TagCloud portlet is very simple We modify the layout file wwwrootblog

protectedviewslayoutsmainphp as follows

ltdiv id=sidebargt

ltphp if(Yiiapp()-gtuser-gtisGuest) $this-gtwidget(rsquoUserMenursquo) gt

ltphp $this-gtwidget(rsquoTagCloudrsquo array(

rsquomaxTagsrsquo=gtYiiapp()-gtparams[rsquotagCloudCountrsquo]

)) gt

ltdivgt

54 Creating Recent Comments Portlet 45

54 Creating Recent Comments Portlet

In this section we create the last portlet that displays a list of comments recently pub-lished

541 Creating RecentComments Class

We create the RecentComments class in the file wwwrootblogprotectedcomponentsRecentCommentsphp The file has the following content

Yiiimport(rsquoziiwidgetsCPortletrsquo)

class RecentComments extends CPortlet

public $title=rsquoRecent Commentsrsquo

public $maxComments=10

public function getRecentComments()

return Commentmodel()-gtfindRecentComments($this-gtmaxComments)

protected function renderContent()

$this-gtrender(rsquorecentCommentsrsquo)

In the above we invoke the findRecentComments method which is defined in the Comment

class as follows

class Comment extends CActiveRecord

public function findRecentComments($limit=10)

return $this-gtwith(rsquopostrsquo)-gtfindAll(array(

rsquoconditionrsquo=gtrsquotstatus=rsquoselfSTATUS APPROVED

rsquoorderrsquo=gtrsquotcreate time DESCrsquo

rsquolimitrsquo=gt$limit

))

46 5 Portlets

542 Creating recentComments View

The recentComments view is saved in the file wwwrootblogprotectedcomponentsviews

recentCommentsphp It simply displays every comment returned by the RecentComments

getRecentComments() method

543 Using RecentComments Portlet

We modify the layout file wwwrootblogprotectedviewslayoutsmainphp to embedthis last portlet

ltdiv id=sidebargt

ltphp if(Yiiapp()-gtuser-gtisGuest) $this-gtwidget(rsquoUserMenursquo) gt

ltphp $this-gtwidget(rsquoTagCloudrsquo array(

rsquomaxTagsrsquo=gtYiiapp()-gtparams[rsquotagCloudCountrsquo]

)) gt

ltphp $this-gtwidget(rsquoRecentCommentsrsquo array(

rsquomaxCommentsrsquo=gtYiiapp()-gtparams[rsquorecentCommentCountrsquo]

)) gt

ltdivgt

Chapter 6

Final Work

61 Beautifying URLs

The URLs linking various pages of our blog application currently look ugly For examplethe URL for the page showing a post looks like the following

indexphpr=postshowampid=1

In this section we describe how to beautify these URLs and make them SEO-friendlyOur goal is to be able to use the following URLs in the application

1 indexphppostsyii leads to the page showing a list of posts with tag yii

2 indexphppost2A+Test+Post leads to the page showing the detail of the postwith ID 2 whose title is A Test Post

3 indexphppostupdate1 leads to the page that allows updating the post with ID1

Note that in the second URL format we include the post title in the URL This is mainlyto make the URL SEO friendly It is said that search engines may also respect the wordsfound in a URL when it is being indexed

To achieve our goal we modify the application configuration as follows

return array(

rsquocomponentsrsquo=gtarray(

rsquourlManagerrsquo=gtarray(

rsquourlFormatrsquo=gtrsquopathrsquo

rsquorulesrsquo=gtarray(

48 6 Final Work

rsquopostltidd+gtlttitlegtrsquo=gtrsquopostviewrsquorsquopostslttaggtrsquo=gtrsquopostindexrsquo

)

)

)

)

In the above we configure the urlManager component by setting its urlFormat propertyto be path and adding a set of rules

The rules are used by urlManager to parse and create the URLs in the desired formatFor example the first rule says that if a URL indexphppostsyii is requested theurlManager component should be responsible to dispatch the request to the route post

index and generate a tag GET parameter with the value yii On the other hand whencreating a URL with the route postindex and parameter tag the urlManager componentwill also use this rule to generate the desired URL indexphppostsyii For this reasonwe say that urlManager is a two-way URL manager

The urlManager component can further beautify our URLs such as hiding indexphp inthe URLs appending suffix like html to the URLs We can obtain these features easilyby configuring various properties of urlManager in the application configuration For moredetails please refer to the Guide

62 Logging Errors

A production Web application often needs sophisticated logging for various events In ourblog application we would like to log the errors occurring when it is being used Sucherrors could be programming mistakes or usersrsquo misuse of the system Logging these errorswill help us to improve the blog application

We enable the error logging by modifying the application configuration as follows

return array(

rsquopreloadrsquo=gtarray(rsquologrsquo)

rsquocomponentsrsquo=gtarray(

rsquologrsquo=gtarray(

rsquoclassrsquo=gtrsquoCLogRouterrsquo

rsquoroutesrsquo=gtarray(

array(

rsquoclassrsquo=gtrsquoCFileLogRoutersquo

rsquolevelsrsquo=gtrsquoerror warningrsquo

63 Final Tune-up and Deployment 49

)

)

)

)

)

With the above configuration if an error or warning occurs detailed information will belogged and saved in a file located under the directory wwwrootblogprotectedruntime

The log component offers more advanced features such as sending log messages to a listof email addresses displaying log messages in JavaScript console window etc For moredetails please refer to the Guide

63 Final Tune-up and Deployment

We are close to finish our blog application Before deployment we would like to do sometune-ups

631 Changing Home Page

We change to use the post list page as the home page We modify the application config-uration as follows

return array(

rsquodefaultControllerrsquo=gtrsquopostrsquo

)

Tip Because PostController already declares list to be its default action whenwe access the home page of the application we will see the result generated by thelist action of the post controller

632 Enabling Schema Caching

Because ActiveRecord relies on the metadata about tables to determine the column infor-mation it takes time to read the metadata and analyze it This may not be a problemduring development stage but for an application running in production mode it is a totalwaste of time if the database schema does not change Therefore we should enable theschema caching by modifying the application configuration as follows

50 6 Final Work

return array(

rsquocomponentsrsquo=gtarray(

rsquocachersquo=gtarray(

rsquoclassrsquo=gtrsquoCDbCachersquo

)

rsquodbrsquo=gtarray(

rsquoclassrsquo=gtrsquosystemdbCDbConnectionrsquo

rsquoconnectionStringrsquo=gtrsquosqlitewwwrootblogprotecteddatablogdbrsquo

rsquoschemaCachingDurationrsquo=gt3600

)

)

)

In the above we first add a cache component which uses a default SQLite database as thecaching storage If our server is equipped with other caching extensions such as APCwe could change to use them as well We also modify the db component by setting itsschemaCachingDuration property to be 3600 which means the parsed database schemadata can remain valid in cache for 3600 seconds

633 Disabling Debugging Mode

We modify the entry script file wwwrootblogindexphp by removing the line definingthe constant YII DEBUG This constant is useful during development stage because it allowsYii to display more debugging information when an error occurs However when theapplication is running in production mode displaying debugging information is not agood idea because it may contain sensitive information such as where the script file islocated and the content in the file etc

634 Deploying the Application

The final deployment process manly involves copying the directory wwwrootblog to thetarget directory The following checklist shows every needed step

1 Install Yii in the target place if it is not available

2 Copy the entire directory wwwrootblog to the target place

3 Edit the entry script file indexphp by pointing the $yii variable to the new Yiibootstrap file

4 Edit the file protectedyiicphp by setting the $yiic variable to be the new Yiiyiicphp file

64 Future Enhancements 51

5 Change the permission of the directories assets and protectedruntime so that theyare writable by the Web server process

64 Future Enhancements

641 Using a Theme

Without writing any code our blog application is already themeable To use a themewe mainly need to develop the theme by writing customized view files in the theme Forexample to use a theme named classic that uses a different page layout we would createa layout view file wwwrootblogthemesclassicviewslayoutsmainphp We also needto change the application configuration to indicate our choice of the classic theme

return array(

rsquothemersquo=gtrsquoclassicrsquo

)

642 Internationalization

We may also internationalize our blog application so that its pages can be displayed indifferent languages This mainly involves efforts in two aspects

First we may create view files in different languages For example for the list pageof PostController we can create a view file wwwrootblogprotectedviewspostzh cn

listphp When the application is configured to use simplified Chinese (the language codeis zh cn) Yii will automatically use this new view file instead of the original one

Second we may create message translations for those messages generated by code Themessage translations should be saved as files under the directory wwwrootblogprotected

messages We also need to modify the code where we use text strings by enclosing themin the method call Yiit()

For more details about internationalization please refer to the Guide

643 Improving Performance with Cache

While the Yii framework itself is very efficient it is not necessarily true that an applicationwritten in Yii efficient There are several places in our blog application that we can improvethe performance For example the tag clould portlet could be one of the performancebottlenecks because it involves complex database query and PHP logic

52 6 Final Work

We can make use of the sophisticated caching feature provided by Yii to improve theperformance One of the most useful components in Yii is COutputCache which caches afragment of page display so that the underlying code generating the fragment does not needto be executed for every request For example in the layout file wwwrootblogprotected

viewslayoutsmainphp we can enclose the tag cloud portlet with COutputCache

ltphp if($this-gtbeginCache(rsquotagCloudrsquo array(rsquodurationrsquo=gt3600))) gt

ltphp $this-gtwidget(rsquoTagCloudrsquo array(

rsquomaxTagsrsquo=gtYiiapp()-gtparams[rsquotagCloudCountrsquo]

)) gt

ltphp $this-gtendCache() gt

With the above code the tag cloud display will be served from cache instead of beinggenerated on-the-fly for every request The cached content will remain valid in cache for3600 seconds

644 Adding New Features

Our blog application only has very basic functionalities To become a complete blogsystem more features are needed for example calendar portlet email notifications postcategorization archived post portlet and so on We will leave the implementation of thesefeatures to interested readers

  • Contents
  • License
  • Getting Started
    • Building a Blog System using Yii
    • Testdriving with Yii
      • Installing Yii
      • Creating Skeleton Application
      • Application Workflow
        • Requirements Analysis
        • Overall Design
          • Initial Prototyping
            • Setting Up Database
              • Creating Database
              • Establishing Database Connection
                • Scaffolding
                • Authenticating User
                • Summary
                  • Post Management
                    • Customizing Post Model
                      • Customizing rules() Method
                      • Customizing relations() Method
                      • Representing Status in Text
                        • Creating and Updating Posts
                          • Customizing Access Control
                          • Customizing create and update Operations
                            • Displaying Posts
                              • Customizing view Operation
                              • Customizing index Operation
                                • Managing Posts
                                  • Listing Posts in Tabular View
                                  • Deleting Posts
                                      • Comment Management
                                        • Customizing Comment Model
                                          • Customizing rules() Method
                                          • Customizing attributeLabels() Method
                                          • Customizing Saving Process
                                            • Creating and Displaying Comments
                                              • Displaying Comments
                                              • Creating Comments
                                                • Managing Comments
                                                  • Updating and Deleting Comments
                                                  • Approving Comments
                                                      • Portlets
                                                        • Customizing Page Layout
                                                        • Creating User Menu Portlet
                                                          • Creating UserMenu Class
                                                          • Creating userMenu View
                                                          • Using UserMenu Portlet
                                                          • Testing UserMenu Portlet
                                                          • Summary
                                                            • Creating Tag Cloud Portlet
                                                              • Creating TagCloud Class
                                                              • Using TagCloud Portlet
                                                                • Creating Recent Comments Portlet
                                                                  • Creating RecentComments Class
                                                                  • Creating recentComments View
                                                                  • Using RecentComments Portlet
                                                                      • Final Work
                                                                        • Beautifying URLs
                                                                        • Logging Errors
                                                                        • Final Tune-up and Deployment
                                                                          • Changing Home Page
                                                                          • Enabling Schema Caching
                                                                          • Disabling Debugging Mode
                                                                          • Deploying the Application
                                                                            • Future Enhancements
                                                                              • Using a Theme
                                                                              • Internationalization
                                                                              • Improving Performance with Cache
                                                                              • Adding New Features
Page 48: Yii Blog 1.1.0 Demo Tutor

52 Creating User Menu Portlet 41

public $layout=rsquocolumn2rsquo

Tip If some view of the controller does not use the same layout as declaredby the layout property it may customize it in the view by a simple assignment$this-gtlayout=rsquonewlayoutrsquo

52 Creating User Menu Portlet

Based on the requirements analysis we need three different portlets the rdquouser menurdquoportlet the rdquotag cloudrdquo portlet and the rdquorecent commentsrdquo portlet We will develop theseportlets by extending the [CPortlet] widget provided by Yii

In this section we will develop our first concrete portlet - the user menu portlet whichdisplays a list of menu items that are only available to authenticated users The menucontains four items

bull Approve Comments a hyperlink that leads to a list of comments pending approval

bull Create New Post a hyperlink that leads to the post creation page

bull Manage Posts a hyperlink that leads to the post management page

bull Logout a link button that would log out the current user

521 Creating UserMenu Class

We create the UserMenu class to represent the logic part of the user menu portlet Theclass is saved in the file wwwrootblogprotectedcomponentsUserMenuphp which has thefollowing content

Yiiimport(rsquoziiwidgetsCPortletrsquo)

class UserMenu extends CPortlet

public function init()

$this-gttitle=CHtmlencode(Yiiapp()-gtuser-gtname)

parentinit()

42 5 Portlets

protected function renderContent()

$this-gtrender(rsquouserMenursquo)

The UserMenu class extends from the CPortlet class that we created previously It overridesboth the init() method and the renderContent() method of CPortlet The former setsthe portlet title to be the name of the current user the latter generates the portlet bodycontent by rendering a view named userMenu

Tip Notice that we have to explicitly include the CPortlet class by calling Yii

import() before we refer to it the first time This is because CPortlet is part of thezii project ndash the official extension library for Yii For performance considerationclasses in this project are not listed as core classes Therefore we have to import itbefore we use it the first time

522 Creating userMenu View

Next we create the userMenu view which is saved in the file wwwrootblogprotected

componentsviewsuserMenuphp

ltulgt

ltligtltphp echo CHtmllink(rsquoCreate New Postrsquoarray(rsquopostcreatersquo)) gtltligt

ltligtltphp echo CHtmllink(rsquoManage Postsrsquoarray(rsquopostadminrsquo)) gtltligt

ltligtltphp echo CHtmllink(rsquoApprove Commentsrsquoarray(rsquocommentindexrsquo))

rsquo (rsquo Commentmodel()-gtpendingCommentCount rsquo)rsquo gtltligt

ltligtltphp echo CHtmllink(rsquoLogoutrsquoarray(rsquositelogoutrsquo)) gtltligt

ltulgt

Info By default view files for a widget should be placed under the views sub-directory of the directory containing the widget class file The file name must bethe same as the view name

523 Using UserMenu Portlet

It is time for us to make use of our newly completed UserMenu portlet We modify thelayout view file wwwrootblogprotectedviewslayoutscolumn2php as follows

53 Creating Tag Cloud Portlet 43

ltdiv id=sidebargt

ltphp if(Yiiapp()-gtuser-gtisGuest) $this-gtwidget(rsquoUserMenursquo) gt

ltdivgt

In the above we call the widget() method to generate and execute an instance of theUserMenu class Because the portlet should only be displayed to authenticated users weonly call widget() when the isGuest property of the current user is false (meaning theuser is authenticated)

524 Testing UserMenu Portlet

Letrsquos test what we have so far

1 Open a browser window and enter the URL httpwwwexamplecomblogindex

php Verify that there is nothing displayed in the side bar section of the page

2 Click on the Login hyperlink and fill out the login form to login If successful verifythat the UserMenu portlet appears in the side bar and the portlet has the usernameas its title

3 Click on the rsquoLogoutrsquo hyperlink in the UserMenu portlet Verify that the logout actionis successful and the UserMenu portlet disappears

525 Summary

What we have created is a portlet that is highly reusable We can easily reuse it in adifferent project with little or no modification Moreover the design of this portlet followsclosely the philosophy that logic and presentation should be separated While we did notpoint this out in the previous sections such practice is used nearly everywhere in a typicalYii application

53 Creating Tag Cloud Portlet

Tag cloud displays a list of post tags with visual decorations hinting the popularity of eachindividual tag

531 Creating TagCloud Class

We create the TagCloud class in the file wwwrootblogprotectedcomponentsTagCloud

php The file has the following content

44 5 Portlets

Yiiimport(rsquoziiwidgetsCPortletrsquo)

class TagCloud extends CPortlet

public $title=rsquoTagsrsquo

public $maxTags=20

protected function renderContent()

$tags=Tagmodel()-gtfindTagWeights($this-gtmaxTags)

foreach($tags as $tag=gt$weight)

$link=CHtmllink(CHtmlencode($tag) array(rsquopostindexrsquorsquotagrsquo=gt$tag))

echo CHtmltag(rsquospanrsquo array(

rsquoclassrsquo=gtrsquotagrsquo

rsquostylersquo=gtfont-size$weightpt) $link)n

Unlike the UserMenu portlet the TagCloud portlet does not use a view Instead its presen-tation is done in the renderContent() method This is because the presentation does notcontain much HTML tags

We display each tag as a hyperlink to the post index page with the corresponding tagparameter The font size of each tag link is adjusted according to their relative weightamong other tags If a tag has higher frequency value than the other it will have a biggerfont size

532 Using TagCloud Portlet

Usage of the TagCloud portlet is very simple We modify the layout file wwwrootblog

protectedviewslayoutsmainphp as follows

ltdiv id=sidebargt

ltphp if(Yiiapp()-gtuser-gtisGuest) $this-gtwidget(rsquoUserMenursquo) gt

ltphp $this-gtwidget(rsquoTagCloudrsquo array(

rsquomaxTagsrsquo=gtYiiapp()-gtparams[rsquotagCloudCountrsquo]

)) gt

ltdivgt

54 Creating Recent Comments Portlet 45

54 Creating Recent Comments Portlet

In this section we create the last portlet that displays a list of comments recently pub-lished

541 Creating RecentComments Class

We create the RecentComments class in the file wwwrootblogprotectedcomponentsRecentCommentsphp The file has the following content

Yiiimport(rsquoziiwidgetsCPortletrsquo)

class RecentComments extends CPortlet

public $title=rsquoRecent Commentsrsquo

public $maxComments=10

public function getRecentComments()

return Commentmodel()-gtfindRecentComments($this-gtmaxComments)

protected function renderContent()

$this-gtrender(rsquorecentCommentsrsquo)

In the above we invoke the findRecentComments method which is defined in the Comment

class as follows

class Comment extends CActiveRecord

public function findRecentComments($limit=10)

return $this-gtwith(rsquopostrsquo)-gtfindAll(array(

rsquoconditionrsquo=gtrsquotstatus=rsquoselfSTATUS APPROVED

rsquoorderrsquo=gtrsquotcreate time DESCrsquo

rsquolimitrsquo=gt$limit

))

46 5 Portlets

542 Creating recentComments View

The recentComments view is saved in the file wwwrootblogprotectedcomponentsviews

recentCommentsphp It simply displays every comment returned by the RecentComments

getRecentComments() method

543 Using RecentComments Portlet

We modify the layout file wwwrootblogprotectedviewslayoutsmainphp to embedthis last portlet

ltdiv id=sidebargt

ltphp if(Yiiapp()-gtuser-gtisGuest) $this-gtwidget(rsquoUserMenursquo) gt

ltphp $this-gtwidget(rsquoTagCloudrsquo array(

rsquomaxTagsrsquo=gtYiiapp()-gtparams[rsquotagCloudCountrsquo]

)) gt

ltphp $this-gtwidget(rsquoRecentCommentsrsquo array(

rsquomaxCommentsrsquo=gtYiiapp()-gtparams[rsquorecentCommentCountrsquo]

)) gt

ltdivgt

Chapter 6

Final Work

61 Beautifying URLs

The URLs linking various pages of our blog application currently look ugly For examplethe URL for the page showing a post looks like the following

indexphpr=postshowampid=1

In this section we describe how to beautify these URLs and make them SEO-friendlyOur goal is to be able to use the following URLs in the application

1 indexphppostsyii leads to the page showing a list of posts with tag yii

2 indexphppost2A+Test+Post leads to the page showing the detail of the postwith ID 2 whose title is A Test Post

3 indexphppostupdate1 leads to the page that allows updating the post with ID1

Note that in the second URL format we include the post title in the URL This is mainlyto make the URL SEO friendly It is said that search engines may also respect the wordsfound in a URL when it is being indexed

To achieve our goal we modify the application configuration as follows

return array(

rsquocomponentsrsquo=gtarray(

rsquourlManagerrsquo=gtarray(

rsquourlFormatrsquo=gtrsquopathrsquo

rsquorulesrsquo=gtarray(

48 6 Final Work

rsquopostltidd+gtlttitlegtrsquo=gtrsquopostviewrsquorsquopostslttaggtrsquo=gtrsquopostindexrsquo

)

)

)

)

In the above we configure the urlManager component by setting its urlFormat propertyto be path and adding a set of rules

The rules are used by urlManager to parse and create the URLs in the desired formatFor example the first rule says that if a URL indexphppostsyii is requested theurlManager component should be responsible to dispatch the request to the route post

index and generate a tag GET parameter with the value yii On the other hand whencreating a URL with the route postindex and parameter tag the urlManager componentwill also use this rule to generate the desired URL indexphppostsyii For this reasonwe say that urlManager is a two-way URL manager

The urlManager component can further beautify our URLs such as hiding indexphp inthe URLs appending suffix like html to the URLs We can obtain these features easilyby configuring various properties of urlManager in the application configuration For moredetails please refer to the Guide

62 Logging Errors

A production Web application often needs sophisticated logging for various events In ourblog application we would like to log the errors occurring when it is being used Sucherrors could be programming mistakes or usersrsquo misuse of the system Logging these errorswill help us to improve the blog application

We enable the error logging by modifying the application configuration as follows

return array(

rsquopreloadrsquo=gtarray(rsquologrsquo)

rsquocomponentsrsquo=gtarray(

rsquologrsquo=gtarray(

rsquoclassrsquo=gtrsquoCLogRouterrsquo

rsquoroutesrsquo=gtarray(

array(

rsquoclassrsquo=gtrsquoCFileLogRoutersquo

rsquolevelsrsquo=gtrsquoerror warningrsquo

63 Final Tune-up and Deployment 49

)

)

)

)

)

With the above configuration if an error or warning occurs detailed information will belogged and saved in a file located under the directory wwwrootblogprotectedruntime

The log component offers more advanced features such as sending log messages to a listof email addresses displaying log messages in JavaScript console window etc For moredetails please refer to the Guide

63 Final Tune-up and Deployment

We are close to finish our blog application Before deployment we would like to do sometune-ups

631 Changing Home Page

We change to use the post list page as the home page We modify the application config-uration as follows

return array(

rsquodefaultControllerrsquo=gtrsquopostrsquo

)

Tip Because PostController already declares list to be its default action whenwe access the home page of the application we will see the result generated by thelist action of the post controller

632 Enabling Schema Caching

Because ActiveRecord relies on the metadata about tables to determine the column infor-mation it takes time to read the metadata and analyze it This may not be a problemduring development stage but for an application running in production mode it is a totalwaste of time if the database schema does not change Therefore we should enable theschema caching by modifying the application configuration as follows

50 6 Final Work

return array(

rsquocomponentsrsquo=gtarray(

rsquocachersquo=gtarray(

rsquoclassrsquo=gtrsquoCDbCachersquo

)

rsquodbrsquo=gtarray(

rsquoclassrsquo=gtrsquosystemdbCDbConnectionrsquo

rsquoconnectionStringrsquo=gtrsquosqlitewwwrootblogprotecteddatablogdbrsquo

rsquoschemaCachingDurationrsquo=gt3600

)

)

)

In the above we first add a cache component which uses a default SQLite database as thecaching storage If our server is equipped with other caching extensions such as APCwe could change to use them as well We also modify the db component by setting itsschemaCachingDuration property to be 3600 which means the parsed database schemadata can remain valid in cache for 3600 seconds

633 Disabling Debugging Mode

We modify the entry script file wwwrootblogindexphp by removing the line definingthe constant YII DEBUG This constant is useful during development stage because it allowsYii to display more debugging information when an error occurs However when theapplication is running in production mode displaying debugging information is not agood idea because it may contain sensitive information such as where the script file islocated and the content in the file etc

634 Deploying the Application

The final deployment process manly involves copying the directory wwwrootblog to thetarget directory The following checklist shows every needed step

1 Install Yii in the target place if it is not available

2 Copy the entire directory wwwrootblog to the target place

3 Edit the entry script file indexphp by pointing the $yii variable to the new Yiibootstrap file

4 Edit the file protectedyiicphp by setting the $yiic variable to be the new Yiiyiicphp file

64 Future Enhancements 51

5 Change the permission of the directories assets and protectedruntime so that theyare writable by the Web server process

64 Future Enhancements

641 Using a Theme

Without writing any code our blog application is already themeable To use a themewe mainly need to develop the theme by writing customized view files in the theme Forexample to use a theme named classic that uses a different page layout we would createa layout view file wwwrootblogthemesclassicviewslayoutsmainphp We also needto change the application configuration to indicate our choice of the classic theme

return array(

rsquothemersquo=gtrsquoclassicrsquo

)

642 Internationalization

We may also internationalize our blog application so that its pages can be displayed indifferent languages This mainly involves efforts in two aspects

First we may create view files in different languages For example for the list pageof PostController we can create a view file wwwrootblogprotectedviewspostzh cn

listphp When the application is configured to use simplified Chinese (the language codeis zh cn) Yii will automatically use this new view file instead of the original one

Second we may create message translations for those messages generated by code Themessage translations should be saved as files under the directory wwwrootblogprotected

messages We also need to modify the code where we use text strings by enclosing themin the method call Yiit()

For more details about internationalization please refer to the Guide

643 Improving Performance with Cache

While the Yii framework itself is very efficient it is not necessarily true that an applicationwritten in Yii efficient There are several places in our blog application that we can improvethe performance For example the tag clould portlet could be one of the performancebottlenecks because it involves complex database query and PHP logic

52 6 Final Work

We can make use of the sophisticated caching feature provided by Yii to improve theperformance One of the most useful components in Yii is COutputCache which caches afragment of page display so that the underlying code generating the fragment does not needto be executed for every request For example in the layout file wwwrootblogprotected

viewslayoutsmainphp we can enclose the tag cloud portlet with COutputCache

ltphp if($this-gtbeginCache(rsquotagCloudrsquo array(rsquodurationrsquo=gt3600))) gt

ltphp $this-gtwidget(rsquoTagCloudrsquo array(

rsquomaxTagsrsquo=gtYiiapp()-gtparams[rsquotagCloudCountrsquo]

)) gt

ltphp $this-gtendCache() gt

With the above code the tag cloud display will be served from cache instead of beinggenerated on-the-fly for every request The cached content will remain valid in cache for3600 seconds

644 Adding New Features

Our blog application only has very basic functionalities To become a complete blogsystem more features are needed for example calendar portlet email notifications postcategorization archived post portlet and so on We will leave the implementation of thesefeatures to interested readers

  • Contents
  • License
  • Getting Started
    • Building a Blog System using Yii
    • Testdriving with Yii
      • Installing Yii
      • Creating Skeleton Application
      • Application Workflow
        • Requirements Analysis
        • Overall Design
          • Initial Prototyping
            • Setting Up Database
              • Creating Database
              • Establishing Database Connection
                • Scaffolding
                • Authenticating User
                • Summary
                  • Post Management
                    • Customizing Post Model
                      • Customizing rules() Method
                      • Customizing relations() Method
                      • Representing Status in Text
                        • Creating and Updating Posts
                          • Customizing Access Control
                          • Customizing create and update Operations
                            • Displaying Posts
                              • Customizing view Operation
                              • Customizing index Operation
                                • Managing Posts
                                  • Listing Posts in Tabular View
                                  • Deleting Posts
                                      • Comment Management
                                        • Customizing Comment Model
                                          • Customizing rules() Method
                                          • Customizing attributeLabels() Method
                                          • Customizing Saving Process
                                            • Creating and Displaying Comments
                                              • Displaying Comments
                                              • Creating Comments
                                                • Managing Comments
                                                  • Updating and Deleting Comments
                                                  • Approving Comments
                                                      • Portlets
                                                        • Customizing Page Layout
                                                        • Creating User Menu Portlet
                                                          • Creating UserMenu Class
                                                          • Creating userMenu View
                                                          • Using UserMenu Portlet
                                                          • Testing UserMenu Portlet
                                                          • Summary
                                                            • Creating Tag Cloud Portlet
                                                              • Creating TagCloud Class
                                                              • Using TagCloud Portlet
                                                                • Creating Recent Comments Portlet
                                                                  • Creating RecentComments Class
                                                                  • Creating recentComments View
                                                                  • Using RecentComments Portlet
                                                                      • Final Work
                                                                        • Beautifying URLs
                                                                        • Logging Errors
                                                                        • Final Tune-up and Deployment
                                                                          • Changing Home Page
                                                                          • Enabling Schema Caching
                                                                          • Disabling Debugging Mode
                                                                          • Deploying the Application
                                                                            • Future Enhancements
                                                                              • Using a Theme
                                                                              • Internationalization
                                                                              • Improving Performance with Cache
                                                                              • Adding New Features
Page 49: Yii Blog 1.1.0 Demo Tutor

42 5 Portlets

protected function renderContent()

$this-gtrender(rsquouserMenursquo)

The UserMenu class extends from the CPortlet class that we created previously It overridesboth the init() method and the renderContent() method of CPortlet The former setsthe portlet title to be the name of the current user the latter generates the portlet bodycontent by rendering a view named userMenu

Tip Notice that we have to explicitly include the CPortlet class by calling Yii

import() before we refer to it the first time This is because CPortlet is part of thezii project ndash the official extension library for Yii For performance considerationclasses in this project are not listed as core classes Therefore we have to import itbefore we use it the first time

522 Creating userMenu View

Next we create the userMenu view which is saved in the file wwwrootblogprotected

componentsviewsuserMenuphp

ltulgt

ltligtltphp echo CHtmllink(rsquoCreate New Postrsquoarray(rsquopostcreatersquo)) gtltligt

ltligtltphp echo CHtmllink(rsquoManage Postsrsquoarray(rsquopostadminrsquo)) gtltligt

ltligtltphp echo CHtmllink(rsquoApprove Commentsrsquoarray(rsquocommentindexrsquo))

rsquo (rsquo Commentmodel()-gtpendingCommentCount rsquo)rsquo gtltligt

ltligtltphp echo CHtmllink(rsquoLogoutrsquoarray(rsquositelogoutrsquo)) gtltligt

ltulgt

Info By default view files for a widget should be placed under the views sub-directory of the directory containing the widget class file The file name must bethe same as the view name

523 Using UserMenu Portlet

It is time for us to make use of our newly completed UserMenu portlet We modify thelayout view file wwwrootblogprotectedviewslayoutscolumn2php as follows

53 Creating Tag Cloud Portlet 43

ltdiv id=sidebargt

ltphp if(Yiiapp()-gtuser-gtisGuest) $this-gtwidget(rsquoUserMenursquo) gt

ltdivgt

In the above we call the widget() method to generate and execute an instance of theUserMenu class Because the portlet should only be displayed to authenticated users weonly call widget() when the isGuest property of the current user is false (meaning theuser is authenticated)

524 Testing UserMenu Portlet

Letrsquos test what we have so far

1 Open a browser window and enter the URL httpwwwexamplecomblogindex

php Verify that there is nothing displayed in the side bar section of the page

2 Click on the Login hyperlink and fill out the login form to login If successful verifythat the UserMenu portlet appears in the side bar and the portlet has the usernameas its title

3 Click on the rsquoLogoutrsquo hyperlink in the UserMenu portlet Verify that the logout actionis successful and the UserMenu portlet disappears

525 Summary

What we have created is a portlet that is highly reusable We can easily reuse it in adifferent project with little or no modification Moreover the design of this portlet followsclosely the philosophy that logic and presentation should be separated While we did notpoint this out in the previous sections such practice is used nearly everywhere in a typicalYii application

53 Creating Tag Cloud Portlet

Tag cloud displays a list of post tags with visual decorations hinting the popularity of eachindividual tag

531 Creating TagCloud Class

We create the TagCloud class in the file wwwrootblogprotectedcomponentsTagCloud

php The file has the following content

44 5 Portlets

Yiiimport(rsquoziiwidgetsCPortletrsquo)

class TagCloud extends CPortlet

public $title=rsquoTagsrsquo

public $maxTags=20

protected function renderContent()

$tags=Tagmodel()-gtfindTagWeights($this-gtmaxTags)

foreach($tags as $tag=gt$weight)

$link=CHtmllink(CHtmlencode($tag) array(rsquopostindexrsquorsquotagrsquo=gt$tag))

echo CHtmltag(rsquospanrsquo array(

rsquoclassrsquo=gtrsquotagrsquo

rsquostylersquo=gtfont-size$weightpt) $link)n

Unlike the UserMenu portlet the TagCloud portlet does not use a view Instead its presen-tation is done in the renderContent() method This is because the presentation does notcontain much HTML tags

We display each tag as a hyperlink to the post index page with the corresponding tagparameter The font size of each tag link is adjusted according to their relative weightamong other tags If a tag has higher frequency value than the other it will have a biggerfont size

532 Using TagCloud Portlet

Usage of the TagCloud portlet is very simple We modify the layout file wwwrootblog

protectedviewslayoutsmainphp as follows

ltdiv id=sidebargt

ltphp if(Yiiapp()-gtuser-gtisGuest) $this-gtwidget(rsquoUserMenursquo) gt

ltphp $this-gtwidget(rsquoTagCloudrsquo array(

rsquomaxTagsrsquo=gtYiiapp()-gtparams[rsquotagCloudCountrsquo]

)) gt

ltdivgt

54 Creating Recent Comments Portlet 45

54 Creating Recent Comments Portlet

In this section we create the last portlet that displays a list of comments recently pub-lished

541 Creating RecentComments Class

We create the RecentComments class in the file wwwrootblogprotectedcomponentsRecentCommentsphp The file has the following content

Yiiimport(rsquoziiwidgetsCPortletrsquo)

class RecentComments extends CPortlet

public $title=rsquoRecent Commentsrsquo

public $maxComments=10

public function getRecentComments()

return Commentmodel()-gtfindRecentComments($this-gtmaxComments)

protected function renderContent()

$this-gtrender(rsquorecentCommentsrsquo)

In the above we invoke the findRecentComments method which is defined in the Comment

class as follows

class Comment extends CActiveRecord

public function findRecentComments($limit=10)

return $this-gtwith(rsquopostrsquo)-gtfindAll(array(

rsquoconditionrsquo=gtrsquotstatus=rsquoselfSTATUS APPROVED

rsquoorderrsquo=gtrsquotcreate time DESCrsquo

rsquolimitrsquo=gt$limit

))

46 5 Portlets

542 Creating recentComments View

The recentComments view is saved in the file wwwrootblogprotectedcomponentsviews

recentCommentsphp It simply displays every comment returned by the RecentComments

getRecentComments() method

543 Using RecentComments Portlet

We modify the layout file wwwrootblogprotectedviewslayoutsmainphp to embedthis last portlet

ltdiv id=sidebargt

ltphp if(Yiiapp()-gtuser-gtisGuest) $this-gtwidget(rsquoUserMenursquo) gt

ltphp $this-gtwidget(rsquoTagCloudrsquo array(

rsquomaxTagsrsquo=gtYiiapp()-gtparams[rsquotagCloudCountrsquo]

)) gt

ltphp $this-gtwidget(rsquoRecentCommentsrsquo array(

rsquomaxCommentsrsquo=gtYiiapp()-gtparams[rsquorecentCommentCountrsquo]

)) gt

ltdivgt

Chapter 6

Final Work

61 Beautifying URLs

The URLs linking various pages of our blog application currently look ugly For examplethe URL for the page showing a post looks like the following

indexphpr=postshowampid=1

In this section we describe how to beautify these URLs and make them SEO-friendlyOur goal is to be able to use the following URLs in the application

1 indexphppostsyii leads to the page showing a list of posts with tag yii

2 indexphppost2A+Test+Post leads to the page showing the detail of the postwith ID 2 whose title is A Test Post

3 indexphppostupdate1 leads to the page that allows updating the post with ID1

Note that in the second URL format we include the post title in the URL This is mainlyto make the URL SEO friendly It is said that search engines may also respect the wordsfound in a URL when it is being indexed

To achieve our goal we modify the application configuration as follows

return array(

rsquocomponentsrsquo=gtarray(

rsquourlManagerrsquo=gtarray(

rsquourlFormatrsquo=gtrsquopathrsquo

rsquorulesrsquo=gtarray(

48 6 Final Work

rsquopostltidd+gtlttitlegtrsquo=gtrsquopostviewrsquorsquopostslttaggtrsquo=gtrsquopostindexrsquo

)

)

)

)

In the above we configure the urlManager component by setting its urlFormat propertyto be path and adding a set of rules

The rules are used by urlManager to parse and create the URLs in the desired formatFor example the first rule says that if a URL indexphppostsyii is requested theurlManager component should be responsible to dispatch the request to the route post

index and generate a tag GET parameter with the value yii On the other hand whencreating a URL with the route postindex and parameter tag the urlManager componentwill also use this rule to generate the desired URL indexphppostsyii For this reasonwe say that urlManager is a two-way URL manager

The urlManager component can further beautify our URLs such as hiding indexphp inthe URLs appending suffix like html to the URLs We can obtain these features easilyby configuring various properties of urlManager in the application configuration For moredetails please refer to the Guide

62 Logging Errors

A production Web application often needs sophisticated logging for various events In ourblog application we would like to log the errors occurring when it is being used Sucherrors could be programming mistakes or usersrsquo misuse of the system Logging these errorswill help us to improve the blog application

We enable the error logging by modifying the application configuration as follows

return array(

rsquopreloadrsquo=gtarray(rsquologrsquo)

rsquocomponentsrsquo=gtarray(

rsquologrsquo=gtarray(

rsquoclassrsquo=gtrsquoCLogRouterrsquo

rsquoroutesrsquo=gtarray(

array(

rsquoclassrsquo=gtrsquoCFileLogRoutersquo

rsquolevelsrsquo=gtrsquoerror warningrsquo

63 Final Tune-up and Deployment 49

)

)

)

)

)

With the above configuration if an error or warning occurs detailed information will belogged and saved in a file located under the directory wwwrootblogprotectedruntime

The log component offers more advanced features such as sending log messages to a listof email addresses displaying log messages in JavaScript console window etc For moredetails please refer to the Guide

63 Final Tune-up and Deployment

We are close to finish our blog application Before deployment we would like to do sometune-ups

631 Changing Home Page

We change to use the post list page as the home page We modify the application config-uration as follows

return array(

rsquodefaultControllerrsquo=gtrsquopostrsquo

)

Tip Because PostController already declares list to be its default action whenwe access the home page of the application we will see the result generated by thelist action of the post controller

632 Enabling Schema Caching

Because ActiveRecord relies on the metadata about tables to determine the column infor-mation it takes time to read the metadata and analyze it This may not be a problemduring development stage but for an application running in production mode it is a totalwaste of time if the database schema does not change Therefore we should enable theschema caching by modifying the application configuration as follows

50 6 Final Work

return array(

rsquocomponentsrsquo=gtarray(

rsquocachersquo=gtarray(

rsquoclassrsquo=gtrsquoCDbCachersquo

)

rsquodbrsquo=gtarray(

rsquoclassrsquo=gtrsquosystemdbCDbConnectionrsquo

rsquoconnectionStringrsquo=gtrsquosqlitewwwrootblogprotecteddatablogdbrsquo

rsquoschemaCachingDurationrsquo=gt3600

)

)

)

In the above we first add a cache component which uses a default SQLite database as thecaching storage If our server is equipped with other caching extensions such as APCwe could change to use them as well We also modify the db component by setting itsschemaCachingDuration property to be 3600 which means the parsed database schemadata can remain valid in cache for 3600 seconds

633 Disabling Debugging Mode

We modify the entry script file wwwrootblogindexphp by removing the line definingthe constant YII DEBUG This constant is useful during development stage because it allowsYii to display more debugging information when an error occurs However when theapplication is running in production mode displaying debugging information is not agood idea because it may contain sensitive information such as where the script file islocated and the content in the file etc

634 Deploying the Application

The final deployment process manly involves copying the directory wwwrootblog to thetarget directory The following checklist shows every needed step

1 Install Yii in the target place if it is not available

2 Copy the entire directory wwwrootblog to the target place

3 Edit the entry script file indexphp by pointing the $yii variable to the new Yiibootstrap file

4 Edit the file protectedyiicphp by setting the $yiic variable to be the new Yiiyiicphp file

64 Future Enhancements 51

5 Change the permission of the directories assets and protectedruntime so that theyare writable by the Web server process

64 Future Enhancements

641 Using a Theme

Without writing any code our blog application is already themeable To use a themewe mainly need to develop the theme by writing customized view files in the theme Forexample to use a theme named classic that uses a different page layout we would createa layout view file wwwrootblogthemesclassicviewslayoutsmainphp We also needto change the application configuration to indicate our choice of the classic theme

return array(

rsquothemersquo=gtrsquoclassicrsquo

)

642 Internationalization

We may also internationalize our blog application so that its pages can be displayed indifferent languages This mainly involves efforts in two aspects

First we may create view files in different languages For example for the list pageof PostController we can create a view file wwwrootblogprotectedviewspostzh cn

listphp When the application is configured to use simplified Chinese (the language codeis zh cn) Yii will automatically use this new view file instead of the original one

Second we may create message translations for those messages generated by code Themessage translations should be saved as files under the directory wwwrootblogprotected

messages We also need to modify the code where we use text strings by enclosing themin the method call Yiit()

For more details about internationalization please refer to the Guide

643 Improving Performance with Cache

While the Yii framework itself is very efficient it is not necessarily true that an applicationwritten in Yii efficient There are several places in our blog application that we can improvethe performance For example the tag clould portlet could be one of the performancebottlenecks because it involves complex database query and PHP logic

52 6 Final Work

We can make use of the sophisticated caching feature provided by Yii to improve theperformance One of the most useful components in Yii is COutputCache which caches afragment of page display so that the underlying code generating the fragment does not needto be executed for every request For example in the layout file wwwrootblogprotected

viewslayoutsmainphp we can enclose the tag cloud portlet with COutputCache

ltphp if($this-gtbeginCache(rsquotagCloudrsquo array(rsquodurationrsquo=gt3600))) gt

ltphp $this-gtwidget(rsquoTagCloudrsquo array(

rsquomaxTagsrsquo=gtYiiapp()-gtparams[rsquotagCloudCountrsquo]

)) gt

ltphp $this-gtendCache() gt

With the above code the tag cloud display will be served from cache instead of beinggenerated on-the-fly for every request The cached content will remain valid in cache for3600 seconds

644 Adding New Features

Our blog application only has very basic functionalities To become a complete blogsystem more features are needed for example calendar portlet email notifications postcategorization archived post portlet and so on We will leave the implementation of thesefeatures to interested readers

  • Contents
  • License
  • Getting Started
    • Building a Blog System using Yii
    • Testdriving with Yii
      • Installing Yii
      • Creating Skeleton Application
      • Application Workflow
        • Requirements Analysis
        • Overall Design
          • Initial Prototyping
            • Setting Up Database
              • Creating Database
              • Establishing Database Connection
                • Scaffolding
                • Authenticating User
                • Summary
                  • Post Management
                    • Customizing Post Model
                      • Customizing rules() Method
                      • Customizing relations() Method
                      • Representing Status in Text
                        • Creating and Updating Posts
                          • Customizing Access Control
                          • Customizing create and update Operations
                            • Displaying Posts
                              • Customizing view Operation
                              • Customizing index Operation
                                • Managing Posts
                                  • Listing Posts in Tabular View
                                  • Deleting Posts
                                      • Comment Management
                                        • Customizing Comment Model
                                          • Customizing rules() Method
                                          • Customizing attributeLabels() Method
                                          • Customizing Saving Process
                                            • Creating and Displaying Comments
                                              • Displaying Comments
                                              • Creating Comments
                                                • Managing Comments
                                                  • Updating and Deleting Comments
                                                  • Approving Comments
                                                      • Portlets
                                                        • Customizing Page Layout
                                                        • Creating User Menu Portlet
                                                          • Creating UserMenu Class
                                                          • Creating userMenu View
                                                          • Using UserMenu Portlet
                                                          • Testing UserMenu Portlet
                                                          • Summary
                                                            • Creating Tag Cloud Portlet
                                                              • Creating TagCloud Class
                                                              • Using TagCloud Portlet
                                                                • Creating Recent Comments Portlet
                                                                  • Creating RecentComments Class
                                                                  • Creating recentComments View
                                                                  • Using RecentComments Portlet
                                                                      • Final Work
                                                                        • Beautifying URLs
                                                                        • Logging Errors
                                                                        • Final Tune-up and Deployment
                                                                          • Changing Home Page
                                                                          • Enabling Schema Caching
                                                                          • Disabling Debugging Mode
                                                                          • Deploying the Application
                                                                            • Future Enhancements
                                                                              • Using a Theme
                                                                              • Internationalization
                                                                              • Improving Performance with Cache
                                                                              • Adding New Features
Page 50: Yii Blog 1.1.0 Demo Tutor

53 Creating Tag Cloud Portlet 43

ltdiv id=sidebargt

ltphp if(Yiiapp()-gtuser-gtisGuest) $this-gtwidget(rsquoUserMenursquo) gt

ltdivgt

In the above we call the widget() method to generate and execute an instance of theUserMenu class Because the portlet should only be displayed to authenticated users weonly call widget() when the isGuest property of the current user is false (meaning theuser is authenticated)

524 Testing UserMenu Portlet

Letrsquos test what we have so far

1 Open a browser window and enter the URL httpwwwexamplecomblogindex

php Verify that there is nothing displayed in the side bar section of the page

2 Click on the Login hyperlink and fill out the login form to login If successful verifythat the UserMenu portlet appears in the side bar and the portlet has the usernameas its title

3 Click on the rsquoLogoutrsquo hyperlink in the UserMenu portlet Verify that the logout actionis successful and the UserMenu portlet disappears

525 Summary

What we have created is a portlet that is highly reusable We can easily reuse it in adifferent project with little or no modification Moreover the design of this portlet followsclosely the philosophy that logic and presentation should be separated While we did notpoint this out in the previous sections such practice is used nearly everywhere in a typicalYii application

53 Creating Tag Cloud Portlet

Tag cloud displays a list of post tags with visual decorations hinting the popularity of eachindividual tag

531 Creating TagCloud Class

We create the TagCloud class in the file wwwrootblogprotectedcomponentsTagCloud

php The file has the following content

44 5 Portlets

Yiiimport(rsquoziiwidgetsCPortletrsquo)

class TagCloud extends CPortlet

public $title=rsquoTagsrsquo

public $maxTags=20

protected function renderContent()

$tags=Tagmodel()-gtfindTagWeights($this-gtmaxTags)

foreach($tags as $tag=gt$weight)

$link=CHtmllink(CHtmlencode($tag) array(rsquopostindexrsquorsquotagrsquo=gt$tag))

echo CHtmltag(rsquospanrsquo array(

rsquoclassrsquo=gtrsquotagrsquo

rsquostylersquo=gtfont-size$weightpt) $link)n

Unlike the UserMenu portlet the TagCloud portlet does not use a view Instead its presen-tation is done in the renderContent() method This is because the presentation does notcontain much HTML tags

We display each tag as a hyperlink to the post index page with the corresponding tagparameter The font size of each tag link is adjusted according to their relative weightamong other tags If a tag has higher frequency value than the other it will have a biggerfont size

532 Using TagCloud Portlet

Usage of the TagCloud portlet is very simple We modify the layout file wwwrootblog

protectedviewslayoutsmainphp as follows

ltdiv id=sidebargt

ltphp if(Yiiapp()-gtuser-gtisGuest) $this-gtwidget(rsquoUserMenursquo) gt

ltphp $this-gtwidget(rsquoTagCloudrsquo array(

rsquomaxTagsrsquo=gtYiiapp()-gtparams[rsquotagCloudCountrsquo]

)) gt

ltdivgt

54 Creating Recent Comments Portlet 45

54 Creating Recent Comments Portlet

In this section we create the last portlet that displays a list of comments recently pub-lished

541 Creating RecentComments Class

We create the RecentComments class in the file wwwrootblogprotectedcomponentsRecentCommentsphp The file has the following content

Yiiimport(rsquoziiwidgetsCPortletrsquo)

class RecentComments extends CPortlet

public $title=rsquoRecent Commentsrsquo

public $maxComments=10

public function getRecentComments()

return Commentmodel()-gtfindRecentComments($this-gtmaxComments)

protected function renderContent()

$this-gtrender(rsquorecentCommentsrsquo)

In the above we invoke the findRecentComments method which is defined in the Comment

class as follows

class Comment extends CActiveRecord

public function findRecentComments($limit=10)

return $this-gtwith(rsquopostrsquo)-gtfindAll(array(

rsquoconditionrsquo=gtrsquotstatus=rsquoselfSTATUS APPROVED

rsquoorderrsquo=gtrsquotcreate time DESCrsquo

rsquolimitrsquo=gt$limit

))

46 5 Portlets

542 Creating recentComments View

The recentComments view is saved in the file wwwrootblogprotectedcomponentsviews

recentCommentsphp It simply displays every comment returned by the RecentComments

getRecentComments() method

543 Using RecentComments Portlet

We modify the layout file wwwrootblogprotectedviewslayoutsmainphp to embedthis last portlet

ltdiv id=sidebargt

ltphp if(Yiiapp()-gtuser-gtisGuest) $this-gtwidget(rsquoUserMenursquo) gt

ltphp $this-gtwidget(rsquoTagCloudrsquo array(

rsquomaxTagsrsquo=gtYiiapp()-gtparams[rsquotagCloudCountrsquo]

)) gt

ltphp $this-gtwidget(rsquoRecentCommentsrsquo array(

rsquomaxCommentsrsquo=gtYiiapp()-gtparams[rsquorecentCommentCountrsquo]

)) gt

ltdivgt

Chapter 6

Final Work

61 Beautifying URLs

The URLs linking various pages of our blog application currently look ugly For examplethe URL for the page showing a post looks like the following

indexphpr=postshowampid=1

In this section we describe how to beautify these URLs and make them SEO-friendlyOur goal is to be able to use the following URLs in the application

1 indexphppostsyii leads to the page showing a list of posts with tag yii

2 indexphppost2A+Test+Post leads to the page showing the detail of the postwith ID 2 whose title is A Test Post

3 indexphppostupdate1 leads to the page that allows updating the post with ID1

Note that in the second URL format we include the post title in the URL This is mainlyto make the URL SEO friendly It is said that search engines may also respect the wordsfound in a URL when it is being indexed

To achieve our goal we modify the application configuration as follows

return array(

rsquocomponentsrsquo=gtarray(

rsquourlManagerrsquo=gtarray(

rsquourlFormatrsquo=gtrsquopathrsquo

rsquorulesrsquo=gtarray(

48 6 Final Work

rsquopostltidd+gtlttitlegtrsquo=gtrsquopostviewrsquorsquopostslttaggtrsquo=gtrsquopostindexrsquo

)

)

)

)

In the above we configure the urlManager component by setting its urlFormat propertyto be path and adding a set of rules

The rules are used by urlManager to parse and create the URLs in the desired formatFor example the first rule says that if a URL indexphppostsyii is requested theurlManager component should be responsible to dispatch the request to the route post

index and generate a tag GET parameter with the value yii On the other hand whencreating a URL with the route postindex and parameter tag the urlManager componentwill also use this rule to generate the desired URL indexphppostsyii For this reasonwe say that urlManager is a two-way URL manager

The urlManager component can further beautify our URLs such as hiding indexphp inthe URLs appending suffix like html to the URLs We can obtain these features easilyby configuring various properties of urlManager in the application configuration For moredetails please refer to the Guide

62 Logging Errors

A production Web application often needs sophisticated logging for various events In ourblog application we would like to log the errors occurring when it is being used Sucherrors could be programming mistakes or usersrsquo misuse of the system Logging these errorswill help us to improve the blog application

We enable the error logging by modifying the application configuration as follows

return array(

rsquopreloadrsquo=gtarray(rsquologrsquo)

rsquocomponentsrsquo=gtarray(

rsquologrsquo=gtarray(

rsquoclassrsquo=gtrsquoCLogRouterrsquo

rsquoroutesrsquo=gtarray(

array(

rsquoclassrsquo=gtrsquoCFileLogRoutersquo

rsquolevelsrsquo=gtrsquoerror warningrsquo

63 Final Tune-up and Deployment 49

)

)

)

)

)

With the above configuration if an error or warning occurs detailed information will belogged and saved in a file located under the directory wwwrootblogprotectedruntime

The log component offers more advanced features such as sending log messages to a listof email addresses displaying log messages in JavaScript console window etc For moredetails please refer to the Guide

63 Final Tune-up and Deployment

We are close to finish our blog application Before deployment we would like to do sometune-ups

631 Changing Home Page

We change to use the post list page as the home page We modify the application config-uration as follows

return array(

rsquodefaultControllerrsquo=gtrsquopostrsquo

)

Tip Because PostController already declares list to be its default action whenwe access the home page of the application we will see the result generated by thelist action of the post controller

632 Enabling Schema Caching

Because ActiveRecord relies on the metadata about tables to determine the column infor-mation it takes time to read the metadata and analyze it This may not be a problemduring development stage but for an application running in production mode it is a totalwaste of time if the database schema does not change Therefore we should enable theschema caching by modifying the application configuration as follows

50 6 Final Work

return array(

rsquocomponentsrsquo=gtarray(

rsquocachersquo=gtarray(

rsquoclassrsquo=gtrsquoCDbCachersquo

)

rsquodbrsquo=gtarray(

rsquoclassrsquo=gtrsquosystemdbCDbConnectionrsquo

rsquoconnectionStringrsquo=gtrsquosqlitewwwrootblogprotecteddatablogdbrsquo

rsquoschemaCachingDurationrsquo=gt3600

)

)

)

In the above we first add a cache component which uses a default SQLite database as thecaching storage If our server is equipped with other caching extensions such as APCwe could change to use them as well We also modify the db component by setting itsschemaCachingDuration property to be 3600 which means the parsed database schemadata can remain valid in cache for 3600 seconds

633 Disabling Debugging Mode

We modify the entry script file wwwrootblogindexphp by removing the line definingthe constant YII DEBUG This constant is useful during development stage because it allowsYii to display more debugging information when an error occurs However when theapplication is running in production mode displaying debugging information is not agood idea because it may contain sensitive information such as where the script file islocated and the content in the file etc

634 Deploying the Application

The final deployment process manly involves copying the directory wwwrootblog to thetarget directory The following checklist shows every needed step

1 Install Yii in the target place if it is not available

2 Copy the entire directory wwwrootblog to the target place

3 Edit the entry script file indexphp by pointing the $yii variable to the new Yiibootstrap file

4 Edit the file protectedyiicphp by setting the $yiic variable to be the new Yiiyiicphp file

64 Future Enhancements 51

5 Change the permission of the directories assets and protectedruntime so that theyare writable by the Web server process

64 Future Enhancements

641 Using a Theme

Without writing any code our blog application is already themeable To use a themewe mainly need to develop the theme by writing customized view files in the theme Forexample to use a theme named classic that uses a different page layout we would createa layout view file wwwrootblogthemesclassicviewslayoutsmainphp We also needto change the application configuration to indicate our choice of the classic theme

return array(

rsquothemersquo=gtrsquoclassicrsquo

)

642 Internationalization

We may also internationalize our blog application so that its pages can be displayed indifferent languages This mainly involves efforts in two aspects

First we may create view files in different languages For example for the list pageof PostController we can create a view file wwwrootblogprotectedviewspostzh cn

listphp When the application is configured to use simplified Chinese (the language codeis zh cn) Yii will automatically use this new view file instead of the original one

Second we may create message translations for those messages generated by code Themessage translations should be saved as files under the directory wwwrootblogprotected

messages We also need to modify the code where we use text strings by enclosing themin the method call Yiit()

For more details about internationalization please refer to the Guide

643 Improving Performance with Cache

While the Yii framework itself is very efficient it is not necessarily true that an applicationwritten in Yii efficient There are several places in our blog application that we can improvethe performance For example the tag clould portlet could be one of the performancebottlenecks because it involves complex database query and PHP logic

52 6 Final Work

We can make use of the sophisticated caching feature provided by Yii to improve theperformance One of the most useful components in Yii is COutputCache which caches afragment of page display so that the underlying code generating the fragment does not needto be executed for every request For example in the layout file wwwrootblogprotected

viewslayoutsmainphp we can enclose the tag cloud portlet with COutputCache

ltphp if($this-gtbeginCache(rsquotagCloudrsquo array(rsquodurationrsquo=gt3600))) gt

ltphp $this-gtwidget(rsquoTagCloudrsquo array(

rsquomaxTagsrsquo=gtYiiapp()-gtparams[rsquotagCloudCountrsquo]

)) gt

ltphp $this-gtendCache() gt

With the above code the tag cloud display will be served from cache instead of beinggenerated on-the-fly for every request The cached content will remain valid in cache for3600 seconds

644 Adding New Features

Our blog application only has very basic functionalities To become a complete blogsystem more features are needed for example calendar portlet email notifications postcategorization archived post portlet and so on We will leave the implementation of thesefeatures to interested readers

  • Contents
  • License
  • Getting Started
    • Building a Blog System using Yii
    • Testdriving with Yii
      • Installing Yii
      • Creating Skeleton Application
      • Application Workflow
        • Requirements Analysis
        • Overall Design
          • Initial Prototyping
            • Setting Up Database
              • Creating Database
              • Establishing Database Connection
                • Scaffolding
                • Authenticating User
                • Summary
                  • Post Management
                    • Customizing Post Model
                      • Customizing rules() Method
                      • Customizing relations() Method
                      • Representing Status in Text
                        • Creating and Updating Posts
                          • Customizing Access Control
                          • Customizing create and update Operations
                            • Displaying Posts
                              • Customizing view Operation
                              • Customizing index Operation
                                • Managing Posts
                                  • Listing Posts in Tabular View
                                  • Deleting Posts
                                      • Comment Management
                                        • Customizing Comment Model
                                          • Customizing rules() Method
                                          • Customizing attributeLabels() Method
                                          • Customizing Saving Process
                                            • Creating and Displaying Comments
                                              • Displaying Comments
                                              • Creating Comments
                                                • Managing Comments
                                                  • Updating and Deleting Comments
                                                  • Approving Comments
                                                      • Portlets
                                                        • Customizing Page Layout
                                                        • Creating User Menu Portlet
                                                          • Creating UserMenu Class
                                                          • Creating userMenu View
                                                          • Using UserMenu Portlet
                                                          • Testing UserMenu Portlet
                                                          • Summary
                                                            • Creating Tag Cloud Portlet
                                                              • Creating TagCloud Class
                                                              • Using TagCloud Portlet
                                                                • Creating Recent Comments Portlet
                                                                  • Creating RecentComments Class
                                                                  • Creating recentComments View
                                                                  • Using RecentComments Portlet
                                                                      • Final Work
                                                                        • Beautifying URLs
                                                                        • Logging Errors
                                                                        • Final Tune-up and Deployment
                                                                          • Changing Home Page
                                                                          • Enabling Schema Caching
                                                                          • Disabling Debugging Mode
                                                                          • Deploying the Application
                                                                            • Future Enhancements
                                                                              • Using a Theme
                                                                              • Internationalization
                                                                              • Improving Performance with Cache
                                                                              • Adding New Features
Page 51: Yii Blog 1.1.0 Demo Tutor

44 5 Portlets

Yiiimport(rsquoziiwidgetsCPortletrsquo)

class TagCloud extends CPortlet

public $title=rsquoTagsrsquo

public $maxTags=20

protected function renderContent()

$tags=Tagmodel()-gtfindTagWeights($this-gtmaxTags)

foreach($tags as $tag=gt$weight)

$link=CHtmllink(CHtmlencode($tag) array(rsquopostindexrsquorsquotagrsquo=gt$tag))

echo CHtmltag(rsquospanrsquo array(

rsquoclassrsquo=gtrsquotagrsquo

rsquostylersquo=gtfont-size$weightpt) $link)n

Unlike the UserMenu portlet the TagCloud portlet does not use a view Instead its presen-tation is done in the renderContent() method This is because the presentation does notcontain much HTML tags

We display each tag as a hyperlink to the post index page with the corresponding tagparameter The font size of each tag link is adjusted according to their relative weightamong other tags If a tag has higher frequency value than the other it will have a biggerfont size

532 Using TagCloud Portlet

Usage of the TagCloud portlet is very simple We modify the layout file wwwrootblog

protectedviewslayoutsmainphp as follows

ltdiv id=sidebargt

ltphp if(Yiiapp()-gtuser-gtisGuest) $this-gtwidget(rsquoUserMenursquo) gt

ltphp $this-gtwidget(rsquoTagCloudrsquo array(

rsquomaxTagsrsquo=gtYiiapp()-gtparams[rsquotagCloudCountrsquo]

)) gt

ltdivgt

54 Creating Recent Comments Portlet 45

54 Creating Recent Comments Portlet

In this section we create the last portlet that displays a list of comments recently pub-lished

541 Creating RecentComments Class

We create the RecentComments class in the file wwwrootblogprotectedcomponentsRecentCommentsphp The file has the following content

Yiiimport(rsquoziiwidgetsCPortletrsquo)

class RecentComments extends CPortlet

public $title=rsquoRecent Commentsrsquo

public $maxComments=10

public function getRecentComments()

return Commentmodel()-gtfindRecentComments($this-gtmaxComments)

protected function renderContent()

$this-gtrender(rsquorecentCommentsrsquo)

In the above we invoke the findRecentComments method which is defined in the Comment

class as follows

class Comment extends CActiveRecord

public function findRecentComments($limit=10)

return $this-gtwith(rsquopostrsquo)-gtfindAll(array(

rsquoconditionrsquo=gtrsquotstatus=rsquoselfSTATUS APPROVED

rsquoorderrsquo=gtrsquotcreate time DESCrsquo

rsquolimitrsquo=gt$limit

))

46 5 Portlets

542 Creating recentComments View

The recentComments view is saved in the file wwwrootblogprotectedcomponentsviews

recentCommentsphp It simply displays every comment returned by the RecentComments

getRecentComments() method

543 Using RecentComments Portlet

We modify the layout file wwwrootblogprotectedviewslayoutsmainphp to embedthis last portlet

ltdiv id=sidebargt

ltphp if(Yiiapp()-gtuser-gtisGuest) $this-gtwidget(rsquoUserMenursquo) gt

ltphp $this-gtwidget(rsquoTagCloudrsquo array(

rsquomaxTagsrsquo=gtYiiapp()-gtparams[rsquotagCloudCountrsquo]

)) gt

ltphp $this-gtwidget(rsquoRecentCommentsrsquo array(

rsquomaxCommentsrsquo=gtYiiapp()-gtparams[rsquorecentCommentCountrsquo]

)) gt

ltdivgt

Chapter 6

Final Work

61 Beautifying URLs

The URLs linking various pages of our blog application currently look ugly For examplethe URL for the page showing a post looks like the following

indexphpr=postshowampid=1

In this section we describe how to beautify these URLs and make them SEO-friendlyOur goal is to be able to use the following URLs in the application

1 indexphppostsyii leads to the page showing a list of posts with tag yii

2 indexphppost2A+Test+Post leads to the page showing the detail of the postwith ID 2 whose title is A Test Post

3 indexphppostupdate1 leads to the page that allows updating the post with ID1

Note that in the second URL format we include the post title in the URL This is mainlyto make the URL SEO friendly It is said that search engines may also respect the wordsfound in a URL when it is being indexed

To achieve our goal we modify the application configuration as follows

return array(

rsquocomponentsrsquo=gtarray(

rsquourlManagerrsquo=gtarray(

rsquourlFormatrsquo=gtrsquopathrsquo

rsquorulesrsquo=gtarray(

48 6 Final Work

rsquopostltidd+gtlttitlegtrsquo=gtrsquopostviewrsquorsquopostslttaggtrsquo=gtrsquopostindexrsquo

)

)

)

)

In the above we configure the urlManager component by setting its urlFormat propertyto be path and adding a set of rules

The rules are used by urlManager to parse and create the URLs in the desired formatFor example the first rule says that if a URL indexphppostsyii is requested theurlManager component should be responsible to dispatch the request to the route post

index and generate a tag GET parameter with the value yii On the other hand whencreating a URL with the route postindex and parameter tag the urlManager componentwill also use this rule to generate the desired URL indexphppostsyii For this reasonwe say that urlManager is a two-way URL manager

The urlManager component can further beautify our URLs such as hiding indexphp inthe URLs appending suffix like html to the URLs We can obtain these features easilyby configuring various properties of urlManager in the application configuration For moredetails please refer to the Guide

62 Logging Errors

A production Web application often needs sophisticated logging for various events In ourblog application we would like to log the errors occurring when it is being used Sucherrors could be programming mistakes or usersrsquo misuse of the system Logging these errorswill help us to improve the blog application

We enable the error logging by modifying the application configuration as follows

return array(

rsquopreloadrsquo=gtarray(rsquologrsquo)

rsquocomponentsrsquo=gtarray(

rsquologrsquo=gtarray(

rsquoclassrsquo=gtrsquoCLogRouterrsquo

rsquoroutesrsquo=gtarray(

array(

rsquoclassrsquo=gtrsquoCFileLogRoutersquo

rsquolevelsrsquo=gtrsquoerror warningrsquo

63 Final Tune-up and Deployment 49

)

)

)

)

)

With the above configuration if an error or warning occurs detailed information will belogged and saved in a file located under the directory wwwrootblogprotectedruntime

The log component offers more advanced features such as sending log messages to a listof email addresses displaying log messages in JavaScript console window etc For moredetails please refer to the Guide

63 Final Tune-up and Deployment

We are close to finish our blog application Before deployment we would like to do sometune-ups

631 Changing Home Page

We change to use the post list page as the home page We modify the application config-uration as follows

return array(

rsquodefaultControllerrsquo=gtrsquopostrsquo

)

Tip Because PostController already declares list to be its default action whenwe access the home page of the application we will see the result generated by thelist action of the post controller

632 Enabling Schema Caching

Because ActiveRecord relies on the metadata about tables to determine the column infor-mation it takes time to read the metadata and analyze it This may not be a problemduring development stage but for an application running in production mode it is a totalwaste of time if the database schema does not change Therefore we should enable theschema caching by modifying the application configuration as follows

50 6 Final Work

return array(

rsquocomponentsrsquo=gtarray(

rsquocachersquo=gtarray(

rsquoclassrsquo=gtrsquoCDbCachersquo

)

rsquodbrsquo=gtarray(

rsquoclassrsquo=gtrsquosystemdbCDbConnectionrsquo

rsquoconnectionStringrsquo=gtrsquosqlitewwwrootblogprotecteddatablogdbrsquo

rsquoschemaCachingDurationrsquo=gt3600

)

)

)

In the above we first add a cache component which uses a default SQLite database as thecaching storage If our server is equipped with other caching extensions such as APCwe could change to use them as well We also modify the db component by setting itsschemaCachingDuration property to be 3600 which means the parsed database schemadata can remain valid in cache for 3600 seconds

633 Disabling Debugging Mode

We modify the entry script file wwwrootblogindexphp by removing the line definingthe constant YII DEBUG This constant is useful during development stage because it allowsYii to display more debugging information when an error occurs However when theapplication is running in production mode displaying debugging information is not agood idea because it may contain sensitive information such as where the script file islocated and the content in the file etc

634 Deploying the Application

The final deployment process manly involves copying the directory wwwrootblog to thetarget directory The following checklist shows every needed step

1 Install Yii in the target place if it is not available

2 Copy the entire directory wwwrootblog to the target place

3 Edit the entry script file indexphp by pointing the $yii variable to the new Yiibootstrap file

4 Edit the file protectedyiicphp by setting the $yiic variable to be the new Yiiyiicphp file

64 Future Enhancements 51

5 Change the permission of the directories assets and protectedruntime so that theyare writable by the Web server process

64 Future Enhancements

641 Using a Theme

Without writing any code our blog application is already themeable To use a themewe mainly need to develop the theme by writing customized view files in the theme Forexample to use a theme named classic that uses a different page layout we would createa layout view file wwwrootblogthemesclassicviewslayoutsmainphp We also needto change the application configuration to indicate our choice of the classic theme

return array(

rsquothemersquo=gtrsquoclassicrsquo

)

642 Internationalization

We may also internationalize our blog application so that its pages can be displayed indifferent languages This mainly involves efforts in two aspects

First we may create view files in different languages For example for the list pageof PostController we can create a view file wwwrootblogprotectedviewspostzh cn

listphp When the application is configured to use simplified Chinese (the language codeis zh cn) Yii will automatically use this new view file instead of the original one

Second we may create message translations for those messages generated by code Themessage translations should be saved as files under the directory wwwrootblogprotected

messages We also need to modify the code where we use text strings by enclosing themin the method call Yiit()

For more details about internationalization please refer to the Guide

643 Improving Performance with Cache

While the Yii framework itself is very efficient it is not necessarily true that an applicationwritten in Yii efficient There are several places in our blog application that we can improvethe performance For example the tag clould portlet could be one of the performancebottlenecks because it involves complex database query and PHP logic

52 6 Final Work

We can make use of the sophisticated caching feature provided by Yii to improve theperformance One of the most useful components in Yii is COutputCache which caches afragment of page display so that the underlying code generating the fragment does not needto be executed for every request For example in the layout file wwwrootblogprotected

viewslayoutsmainphp we can enclose the tag cloud portlet with COutputCache

ltphp if($this-gtbeginCache(rsquotagCloudrsquo array(rsquodurationrsquo=gt3600))) gt

ltphp $this-gtwidget(rsquoTagCloudrsquo array(

rsquomaxTagsrsquo=gtYiiapp()-gtparams[rsquotagCloudCountrsquo]

)) gt

ltphp $this-gtendCache() gt

With the above code the tag cloud display will be served from cache instead of beinggenerated on-the-fly for every request The cached content will remain valid in cache for3600 seconds

644 Adding New Features

Our blog application only has very basic functionalities To become a complete blogsystem more features are needed for example calendar portlet email notifications postcategorization archived post portlet and so on We will leave the implementation of thesefeatures to interested readers

  • Contents
  • License
  • Getting Started
    • Building a Blog System using Yii
    • Testdriving with Yii
      • Installing Yii
      • Creating Skeleton Application
      • Application Workflow
        • Requirements Analysis
        • Overall Design
          • Initial Prototyping
            • Setting Up Database
              • Creating Database
              • Establishing Database Connection
                • Scaffolding
                • Authenticating User
                • Summary
                  • Post Management
                    • Customizing Post Model
                      • Customizing rules() Method
                      • Customizing relations() Method
                      • Representing Status in Text
                        • Creating and Updating Posts
                          • Customizing Access Control
                          • Customizing create and update Operations
                            • Displaying Posts
                              • Customizing view Operation
                              • Customizing index Operation
                                • Managing Posts
                                  • Listing Posts in Tabular View
                                  • Deleting Posts
                                      • Comment Management
                                        • Customizing Comment Model
                                          • Customizing rules() Method
                                          • Customizing attributeLabels() Method
                                          • Customizing Saving Process
                                            • Creating and Displaying Comments
                                              • Displaying Comments
                                              • Creating Comments
                                                • Managing Comments
                                                  • Updating and Deleting Comments
                                                  • Approving Comments
                                                      • Portlets
                                                        • Customizing Page Layout
                                                        • Creating User Menu Portlet
                                                          • Creating UserMenu Class
                                                          • Creating userMenu View
                                                          • Using UserMenu Portlet
                                                          • Testing UserMenu Portlet
                                                          • Summary
                                                            • Creating Tag Cloud Portlet
                                                              • Creating TagCloud Class
                                                              • Using TagCloud Portlet
                                                                • Creating Recent Comments Portlet
                                                                  • Creating RecentComments Class
                                                                  • Creating recentComments View
                                                                  • Using RecentComments Portlet
                                                                      • Final Work
                                                                        • Beautifying URLs
                                                                        • Logging Errors
                                                                        • Final Tune-up and Deployment
                                                                          • Changing Home Page
                                                                          • Enabling Schema Caching
                                                                          • Disabling Debugging Mode
                                                                          • Deploying the Application
                                                                            • Future Enhancements
                                                                              • Using a Theme
                                                                              • Internationalization
                                                                              • Improving Performance with Cache
                                                                              • Adding New Features
Page 52: Yii Blog 1.1.0 Demo Tutor

54 Creating Recent Comments Portlet 45

54 Creating Recent Comments Portlet

In this section we create the last portlet that displays a list of comments recently pub-lished

541 Creating RecentComments Class

We create the RecentComments class in the file wwwrootblogprotectedcomponentsRecentCommentsphp The file has the following content

Yiiimport(rsquoziiwidgetsCPortletrsquo)

class RecentComments extends CPortlet

public $title=rsquoRecent Commentsrsquo

public $maxComments=10

public function getRecentComments()

return Commentmodel()-gtfindRecentComments($this-gtmaxComments)

protected function renderContent()

$this-gtrender(rsquorecentCommentsrsquo)

In the above we invoke the findRecentComments method which is defined in the Comment

class as follows

class Comment extends CActiveRecord

public function findRecentComments($limit=10)

return $this-gtwith(rsquopostrsquo)-gtfindAll(array(

rsquoconditionrsquo=gtrsquotstatus=rsquoselfSTATUS APPROVED

rsquoorderrsquo=gtrsquotcreate time DESCrsquo

rsquolimitrsquo=gt$limit

))

46 5 Portlets

542 Creating recentComments View

The recentComments view is saved in the file wwwrootblogprotectedcomponentsviews

recentCommentsphp It simply displays every comment returned by the RecentComments

getRecentComments() method

543 Using RecentComments Portlet

We modify the layout file wwwrootblogprotectedviewslayoutsmainphp to embedthis last portlet

ltdiv id=sidebargt

ltphp if(Yiiapp()-gtuser-gtisGuest) $this-gtwidget(rsquoUserMenursquo) gt

ltphp $this-gtwidget(rsquoTagCloudrsquo array(

rsquomaxTagsrsquo=gtYiiapp()-gtparams[rsquotagCloudCountrsquo]

)) gt

ltphp $this-gtwidget(rsquoRecentCommentsrsquo array(

rsquomaxCommentsrsquo=gtYiiapp()-gtparams[rsquorecentCommentCountrsquo]

)) gt

ltdivgt

Chapter 6

Final Work

61 Beautifying URLs

The URLs linking various pages of our blog application currently look ugly For examplethe URL for the page showing a post looks like the following

indexphpr=postshowampid=1

In this section we describe how to beautify these URLs and make them SEO-friendlyOur goal is to be able to use the following URLs in the application

1 indexphppostsyii leads to the page showing a list of posts with tag yii

2 indexphppost2A+Test+Post leads to the page showing the detail of the postwith ID 2 whose title is A Test Post

3 indexphppostupdate1 leads to the page that allows updating the post with ID1

Note that in the second URL format we include the post title in the URL This is mainlyto make the URL SEO friendly It is said that search engines may also respect the wordsfound in a URL when it is being indexed

To achieve our goal we modify the application configuration as follows

return array(

rsquocomponentsrsquo=gtarray(

rsquourlManagerrsquo=gtarray(

rsquourlFormatrsquo=gtrsquopathrsquo

rsquorulesrsquo=gtarray(

48 6 Final Work

rsquopostltidd+gtlttitlegtrsquo=gtrsquopostviewrsquorsquopostslttaggtrsquo=gtrsquopostindexrsquo

)

)

)

)

In the above we configure the urlManager component by setting its urlFormat propertyto be path and adding a set of rules

The rules are used by urlManager to parse and create the URLs in the desired formatFor example the first rule says that if a URL indexphppostsyii is requested theurlManager component should be responsible to dispatch the request to the route post

index and generate a tag GET parameter with the value yii On the other hand whencreating a URL with the route postindex and parameter tag the urlManager componentwill also use this rule to generate the desired URL indexphppostsyii For this reasonwe say that urlManager is a two-way URL manager

The urlManager component can further beautify our URLs such as hiding indexphp inthe URLs appending suffix like html to the URLs We can obtain these features easilyby configuring various properties of urlManager in the application configuration For moredetails please refer to the Guide

62 Logging Errors

A production Web application often needs sophisticated logging for various events In ourblog application we would like to log the errors occurring when it is being used Sucherrors could be programming mistakes or usersrsquo misuse of the system Logging these errorswill help us to improve the blog application

We enable the error logging by modifying the application configuration as follows

return array(

rsquopreloadrsquo=gtarray(rsquologrsquo)

rsquocomponentsrsquo=gtarray(

rsquologrsquo=gtarray(

rsquoclassrsquo=gtrsquoCLogRouterrsquo

rsquoroutesrsquo=gtarray(

array(

rsquoclassrsquo=gtrsquoCFileLogRoutersquo

rsquolevelsrsquo=gtrsquoerror warningrsquo

63 Final Tune-up and Deployment 49

)

)

)

)

)

With the above configuration if an error or warning occurs detailed information will belogged and saved in a file located under the directory wwwrootblogprotectedruntime

The log component offers more advanced features such as sending log messages to a listof email addresses displaying log messages in JavaScript console window etc For moredetails please refer to the Guide

63 Final Tune-up and Deployment

We are close to finish our blog application Before deployment we would like to do sometune-ups

631 Changing Home Page

We change to use the post list page as the home page We modify the application config-uration as follows

return array(

rsquodefaultControllerrsquo=gtrsquopostrsquo

)

Tip Because PostController already declares list to be its default action whenwe access the home page of the application we will see the result generated by thelist action of the post controller

632 Enabling Schema Caching

Because ActiveRecord relies on the metadata about tables to determine the column infor-mation it takes time to read the metadata and analyze it This may not be a problemduring development stage but for an application running in production mode it is a totalwaste of time if the database schema does not change Therefore we should enable theschema caching by modifying the application configuration as follows

50 6 Final Work

return array(

rsquocomponentsrsquo=gtarray(

rsquocachersquo=gtarray(

rsquoclassrsquo=gtrsquoCDbCachersquo

)

rsquodbrsquo=gtarray(

rsquoclassrsquo=gtrsquosystemdbCDbConnectionrsquo

rsquoconnectionStringrsquo=gtrsquosqlitewwwrootblogprotecteddatablogdbrsquo

rsquoschemaCachingDurationrsquo=gt3600

)

)

)

In the above we first add a cache component which uses a default SQLite database as thecaching storage If our server is equipped with other caching extensions such as APCwe could change to use them as well We also modify the db component by setting itsschemaCachingDuration property to be 3600 which means the parsed database schemadata can remain valid in cache for 3600 seconds

633 Disabling Debugging Mode

We modify the entry script file wwwrootblogindexphp by removing the line definingthe constant YII DEBUG This constant is useful during development stage because it allowsYii to display more debugging information when an error occurs However when theapplication is running in production mode displaying debugging information is not agood idea because it may contain sensitive information such as where the script file islocated and the content in the file etc

634 Deploying the Application

The final deployment process manly involves copying the directory wwwrootblog to thetarget directory The following checklist shows every needed step

1 Install Yii in the target place if it is not available

2 Copy the entire directory wwwrootblog to the target place

3 Edit the entry script file indexphp by pointing the $yii variable to the new Yiibootstrap file

4 Edit the file protectedyiicphp by setting the $yiic variable to be the new Yiiyiicphp file

64 Future Enhancements 51

5 Change the permission of the directories assets and protectedruntime so that theyare writable by the Web server process

64 Future Enhancements

641 Using a Theme

Without writing any code our blog application is already themeable To use a themewe mainly need to develop the theme by writing customized view files in the theme Forexample to use a theme named classic that uses a different page layout we would createa layout view file wwwrootblogthemesclassicviewslayoutsmainphp We also needto change the application configuration to indicate our choice of the classic theme

return array(

rsquothemersquo=gtrsquoclassicrsquo

)

642 Internationalization

We may also internationalize our blog application so that its pages can be displayed indifferent languages This mainly involves efforts in two aspects

First we may create view files in different languages For example for the list pageof PostController we can create a view file wwwrootblogprotectedviewspostzh cn

listphp When the application is configured to use simplified Chinese (the language codeis zh cn) Yii will automatically use this new view file instead of the original one

Second we may create message translations for those messages generated by code Themessage translations should be saved as files under the directory wwwrootblogprotected

messages We also need to modify the code where we use text strings by enclosing themin the method call Yiit()

For more details about internationalization please refer to the Guide

643 Improving Performance with Cache

While the Yii framework itself is very efficient it is not necessarily true that an applicationwritten in Yii efficient There are several places in our blog application that we can improvethe performance For example the tag clould portlet could be one of the performancebottlenecks because it involves complex database query and PHP logic

52 6 Final Work

We can make use of the sophisticated caching feature provided by Yii to improve theperformance One of the most useful components in Yii is COutputCache which caches afragment of page display so that the underlying code generating the fragment does not needto be executed for every request For example in the layout file wwwrootblogprotected

viewslayoutsmainphp we can enclose the tag cloud portlet with COutputCache

ltphp if($this-gtbeginCache(rsquotagCloudrsquo array(rsquodurationrsquo=gt3600))) gt

ltphp $this-gtwidget(rsquoTagCloudrsquo array(

rsquomaxTagsrsquo=gtYiiapp()-gtparams[rsquotagCloudCountrsquo]

)) gt

ltphp $this-gtendCache() gt

With the above code the tag cloud display will be served from cache instead of beinggenerated on-the-fly for every request The cached content will remain valid in cache for3600 seconds

644 Adding New Features

Our blog application only has very basic functionalities To become a complete blogsystem more features are needed for example calendar portlet email notifications postcategorization archived post portlet and so on We will leave the implementation of thesefeatures to interested readers

  • Contents
  • License
  • Getting Started
    • Building a Blog System using Yii
    • Testdriving with Yii
      • Installing Yii
      • Creating Skeleton Application
      • Application Workflow
        • Requirements Analysis
        • Overall Design
          • Initial Prototyping
            • Setting Up Database
              • Creating Database
              • Establishing Database Connection
                • Scaffolding
                • Authenticating User
                • Summary
                  • Post Management
                    • Customizing Post Model
                      • Customizing rules() Method
                      • Customizing relations() Method
                      • Representing Status in Text
                        • Creating and Updating Posts
                          • Customizing Access Control
                          • Customizing create and update Operations
                            • Displaying Posts
                              • Customizing view Operation
                              • Customizing index Operation
                                • Managing Posts
                                  • Listing Posts in Tabular View
                                  • Deleting Posts
                                      • Comment Management
                                        • Customizing Comment Model
                                          • Customizing rules() Method
                                          • Customizing attributeLabels() Method
                                          • Customizing Saving Process
                                            • Creating and Displaying Comments
                                              • Displaying Comments
                                              • Creating Comments
                                                • Managing Comments
                                                  • Updating and Deleting Comments
                                                  • Approving Comments
                                                      • Portlets
                                                        • Customizing Page Layout
                                                        • Creating User Menu Portlet
                                                          • Creating UserMenu Class
                                                          • Creating userMenu View
                                                          • Using UserMenu Portlet
                                                          • Testing UserMenu Portlet
                                                          • Summary
                                                            • Creating Tag Cloud Portlet
                                                              • Creating TagCloud Class
                                                              • Using TagCloud Portlet
                                                                • Creating Recent Comments Portlet
                                                                  • Creating RecentComments Class
                                                                  • Creating recentComments View
                                                                  • Using RecentComments Portlet
                                                                      • Final Work
                                                                        • Beautifying URLs
                                                                        • Logging Errors
                                                                        • Final Tune-up and Deployment
                                                                          • Changing Home Page
                                                                          • Enabling Schema Caching
                                                                          • Disabling Debugging Mode
                                                                          • Deploying the Application
                                                                            • Future Enhancements
                                                                              • Using a Theme
                                                                              • Internationalization
                                                                              • Improving Performance with Cache
                                                                              • Adding New Features
Page 53: Yii Blog 1.1.0 Demo Tutor

46 5 Portlets

542 Creating recentComments View

The recentComments view is saved in the file wwwrootblogprotectedcomponentsviews

recentCommentsphp It simply displays every comment returned by the RecentComments

getRecentComments() method

543 Using RecentComments Portlet

We modify the layout file wwwrootblogprotectedviewslayoutsmainphp to embedthis last portlet

ltdiv id=sidebargt

ltphp if(Yiiapp()-gtuser-gtisGuest) $this-gtwidget(rsquoUserMenursquo) gt

ltphp $this-gtwidget(rsquoTagCloudrsquo array(

rsquomaxTagsrsquo=gtYiiapp()-gtparams[rsquotagCloudCountrsquo]

)) gt

ltphp $this-gtwidget(rsquoRecentCommentsrsquo array(

rsquomaxCommentsrsquo=gtYiiapp()-gtparams[rsquorecentCommentCountrsquo]

)) gt

ltdivgt

Chapter 6

Final Work

61 Beautifying URLs

The URLs linking various pages of our blog application currently look ugly For examplethe URL for the page showing a post looks like the following

indexphpr=postshowampid=1

In this section we describe how to beautify these URLs and make them SEO-friendlyOur goal is to be able to use the following URLs in the application

1 indexphppostsyii leads to the page showing a list of posts with tag yii

2 indexphppost2A+Test+Post leads to the page showing the detail of the postwith ID 2 whose title is A Test Post

3 indexphppostupdate1 leads to the page that allows updating the post with ID1

Note that in the second URL format we include the post title in the URL This is mainlyto make the URL SEO friendly It is said that search engines may also respect the wordsfound in a URL when it is being indexed

To achieve our goal we modify the application configuration as follows

return array(

rsquocomponentsrsquo=gtarray(

rsquourlManagerrsquo=gtarray(

rsquourlFormatrsquo=gtrsquopathrsquo

rsquorulesrsquo=gtarray(

48 6 Final Work

rsquopostltidd+gtlttitlegtrsquo=gtrsquopostviewrsquorsquopostslttaggtrsquo=gtrsquopostindexrsquo

)

)

)

)

In the above we configure the urlManager component by setting its urlFormat propertyto be path and adding a set of rules

The rules are used by urlManager to parse and create the URLs in the desired formatFor example the first rule says that if a URL indexphppostsyii is requested theurlManager component should be responsible to dispatch the request to the route post

index and generate a tag GET parameter with the value yii On the other hand whencreating a URL with the route postindex and parameter tag the urlManager componentwill also use this rule to generate the desired URL indexphppostsyii For this reasonwe say that urlManager is a two-way URL manager

The urlManager component can further beautify our URLs such as hiding indexphp inthe URLs appending suffix like html to the URLs We can obtain these features easilyby configuring various properties of urlManager in the application configuration For moredetails please refer to the Guide

62 Logging Errors

A production Web application often needs sophisticated logging for various events In ourblog application we would like to log the errors occurring when it is being used Sucherrors could be programming mistakes or usersrsquo misuse of the system Logging these errorswill help us to improve the blog application

We enable the error logging by modifying the application configuration as follows

return array(

rsquopreloadrsquo=gtarray(rsquologrsquo)

rsquocomponentsrsquo=gtarray(

rsquologrsquo=gtarray(

rsquoclassrsquo=gtrsquoCLogRouterrsquo

rsquoroutesrsquo=gtarray(

array(

rsquoclassrsquo=gtrsquoCFileLogRoutersquo

rsquolevelsrsquo=gtrsquoerror warningrsquo

63 Final Tune-up and Deployment 49

)

)

)

)

)

With the above configuration if an error or warning occurs detailed information will belogged and saved in a file located under the directory wwwrootblogprotectedruntime

The log component offers more advanced features such as sending log messages to a listof email addresses displaying log messages in JavaScript console window etc For moredetails please refer to the Guide

63 Final Tune-up and Deployment

We are close to finish our blog application Before deployment we would like to do sometune-ups

631 Changing Home Page

We change to use the post list page as the home page We modify the application config-uration as follows

return array(

rsquodefaultControllerrsquo=gtrsquopostrsquo

)

Tip Because PostController already declares list to be its default action whenwe access the home page of the application we will see the result generated by thelist action of the post controller

632 Enabling Schema Caching

Because ActiveRecord relies on the metadata about tables to determine the column infor-mation it takes time to read the metadata and analyze it This may not be a problemduring development stage but for an application running in production mode it is a totalwaste of time if the database schema does not change Therefore we should enable theschema caching by modifying the application configuration as follows

50 6 Final Work

return array(

rsquocomponentsrsquo=gtarray(

rsquocachersquo=gtarray(

rsquoclassrsquo=gtrsquoCDbCachersquo

)

rsquodbrsquo=gtarray(

rsquoclassrsquo=gtrsquosystemdbCDbConnectionrsquo

rsquoconnectionStringrsquo=gtrsquosqlitewwwrootblogprotecteddatablogdbrsquo

rsquoschemaCachingDurationrsquo=gt3600

)

)

)

In the above we first add a cache component which uses a default SQLite database as thecaching storage If our server is equipped with other caching extensions such as APCwe could change to use them as well We also modify the db component by setting itsschemaCachingDuration property to be 3600 which means the parsed database schemadata can remain valid in cache for 3600 seconds

633 Disabling Debugging Mode

We modify the entry script file wwwrootblogindexphp by removing the line definingthe constant YII DEBUG This constant is useful during development stage because it allowsYii to display more debugging information when an error occurs However when theapplication is running in production mode displaying debugging information is not agood idea because it may contain sensitive information such as where the script file islocated and the content in the file etc

634 Deploying the Application

The final deployment process manly involves copying the directory wwwrootblog to thetarget directory The following checklist shows every needed step

1 Install Yii in the target place if it is not available

2 Copy the entire directory wwwrootblog to the target place

3 Edit the entry script file indexphp by pointing the $yii variable to the new Yiibootstrap file

4 Edit the file protectedyiicphp by setting the $yiic variable to be the new Yiiyiicphp file

64 Future Enhancements 51

5 Change the permission of the directories assets and protectedruntime so that theyare writable by the Web server process

64 Future Enhancements

641 Using a Theme

Without writing any code our blog application is already themeable To use a themewe mainly need to develop the theme by writing customized view files in the theme Forexample to use a theme named classic that uses a different page layout we would createa layout view file wwwrootblogthemesclassicviewslayoutsmainphp We also needto change the application configuration to indicate our choice of the classic theme

return array(

rsquothemersquo=gtrsquoclassicrsquo

)

642 Internationalization

We may also internationalize our blog application so that its pages can be displayed indifferent languages This mainly involves efforts in two aspects

First we may create view files in different languages For example for the list pageof PostController we can create a view file wwwrootblogprotectedviewspostzh cn

listphp When the application is configured to use simplified Chinese (the language codeis zh cn) Yii will automatically use this new view file instead of the original one

Second we may create message translations for those messages generated by code Themessage translations should be saved as files under the directory wwwrootblogprotected

messages We also need to modify the code where we use text strings by enclosing themin the method call Yiit()

For more details about internationalization please refer to the Guide

643 Improving Performance with Cache

While the Yii framework itself is very efficient it is not necessarily true that an applicationwritten in Yii efficient There are several places in our blog application that we can improvethe performance For example the tag clould portlet could be one of the performancebottlenecks because it involves complex database query and PHP logic

52 6 Final Work

We can make use of the sophisticated caching feature provided by Yii to improve theperformance One of the most useful components in Yii is COutputCache which caches afragment of page display so that the underlying code generating the fragment does not needto be executed for every request For example in the layout file wwwrootblogprotected

viewslayoutsmainphp we can enclose the tag cloud portlet with COutputCache

ltphp if($this-gtbeginCache(rsquotagCloudrsquo array(rsquodurationrsquo=gt3600))) gt

ltphp $this-gtwidget(rsquoTagCloudrsquo array(

rsquomaxTagsrsquo=gtYiiapp()-gtparams[rsquotagCloudCountrsquo]

)) gt

ltphp $this-gtendCache() gt

With the above code the tag cloud display will be served from cache instead of beinggenerated on-the-fly for every request The cached content will remain valid in cache for3600 seconds

644 Adding New Features

Our blog application only has very basic functionalities To become a complete blogsystem more features are needed for example calendar portlet email notifications postcategorization archived post portlet and so on We will leave the implementation of thesefeatures to interested readers

  • Contents
  • License
  • Getting Started
    • Building a Blog System using Yii
    • Testdriving with Yii
      • Installing Yii
      • Creating Skeleton Application
      • Application Workflow
        • Requirements Analysis
        • Overall Design
          • Initial Prototyping
            • Setting Up Database
              • Creating Database
              • Establishing Database Connection
                • Scaffolding
                • Authenticating User
                • Summary
                  • Post Management
                    • Customizing Post Model
                      • Customizing rules() Method
                      • Customizing relations() Method
                      • Representing Status in Text
                        • Creating and Updating Posts
                          • Customizing Access Control
                          • Customizing create and update Operations
                            • Displaying Posts
                              • Customizing view Operation
                              • Customizing index Operation
                                • Managing Posts
                                  • Listing Posts in Tabular View
                                  • Deleting Posts
                                      • Comment Management
                                        • Customizing Comment Model
                                          • Customizing rules() Method
                                          • Customizing attributeLabels() Method
                                          • Customizing Saving Process
                                            • Creating and Displaying Comments
                                              • Displaying Comments
                                              • Creating Comments
                                                • Managing Comments
                                                  • Updating and Deleting Comments
                                                  • Approving Comments
                                                      • Portlets
                                                        • Customizing Page Layout
                                                        • Creating User Menu Portlet
                                                          • Creating UserMenu Class
                                                          • Creating userMenu View
                                                          • Using UserMenu Portlet
                                                          • Testing UserMenu Portlet
                                                          • Summary
                                                            • Creating Tag Cloud Portlet
                                                              • Creating TagCloud Class
                                                              • Using TagCloud Portlet
                                                                • Creating Recent Comments Portlet
                                                                  • Creating RecentComments Class
                                                                  • Creating recentComments View
                                                                  • Using RecentComments Portlet
                                                                      • Final Work
                                                                        • Beautifying URLs
                                                                        • Logging Errors
                                                                        • Final Tune-up and Deployment
                                                                          • Changing Home Page
                                                                          • Enabling Schema Caching
                                                                          • Disabling Debugging Mode
                                                                          • Deploying the Application
                                                                            • Future Enhancements
                                                                              • Using a Theme
                                                                              • Internationalization
                                                                              • Improving Performance with Cache
                                                                              • Adding New Features
Page 54: Yii Blog 1.1.0 Demo Tutor

Chapter 6

Final Work

61 Beautifying URLs

The URLs linking various pages of our blog application currently look ugly For examplethe URL for the page showing a post looks like the following

indexphpr=postshowampid=1

In this section we describe how to beautify these URLs and make them SEO-friendlyOur goal is to be able to use the following URLs in the application

1 indexphppostsyii leads to the page showing a list of posts with tag yii

2 indexphppost2A+Test+Post leads to the page showing the detail of the postwith ID 2 whose title is A Test Post

3 indexphppostupdate1 leads to the page that allows updating the post with ID1

Note that in the second URL format we include the post title in the URL This is mainlyto make the URL SEO friendly It is said that search engines may also respect the wordsfound in a URL when it is being indexed

To achieve our goal we modify the application configuration as follows

return array(

rsquocomponentsrsquo=gtarray(

rsquourlManagerrsquo=gtarray(

rsquourlFormatrsquo=gtrsquopathrsquo

rsquorulesrsquo=gtarray(

48 6 Final Work

rsquopostltidd+gtlttitlegtrsquo=gtrsquopostviewrsquorsquopostslttaggtrsquo=gtrsquopostindexrsquo

)

)

)

)

In the above we configure the urlManager component by setting its urlFormat propertyto be path and adding a set of rules

The rules are used by urlManager to parse and create the URLs in the desired formatFor example the first rule says that if a URL indexphppostsyii is requested theurlManager component should be responsible to dispatch the request to the route post

index and generate a tag GET parameter with the value yii On the other hand whencreating a URL with the route postindex and parameter tag the urlManager componentwill also use this rule to generate the desired URL indexphppostsyii For this reasonwe say that urlManager is a two-way URL manager

The urlManager component can further beautify our URLs such as hiding indexphp inthe URLs appending suffix like html to the URLs We can obtain these features easilyby configuring various properties of urlManager in the application configuration For moredetails please refer to the Guide

62 Logging Errors

A production Web application often needs sophisticated logging for various events In ourblog application we would like to log the errors occurring when it is being used Sucherrors could be programming mistakes or usersrsquo misuse of the system Logging these errorswill help us to improve the blog application

We enable the error logging by modifying the application configuration as follows

return array(

rsquopreloadrsquo=gtarray(rsquologrsquo)

rsquocomponentsrsquo=gtarray(

rsquologrsquo=gtarray(

rsquoclassrsquo=gtrsquoCLogRouterrsquo

rsquoroutesrsquo=gtarray(

array(

rsquoclassrsquo=gtrsquoCFileLogRoutersquo

rsquolevelsrsquo=gtrsquoerror warningrsquo

63 Final Tune-up and Deployment 49

)

)

)

)

)

With the above configuration if an error or warning occurs detailed information will belogged and saved in a file located under the directory wwwrootblogprotectedruntime

The log component offers more advanced features such as sending log messages to a listof email addresses displaying log messages in JavaScript console window etc For moredetails please refer to the Guide

63 Final Tune-up and Deployment

We are close to finish our blog application Before deployment we would like to do sometune-ups

631 Changing Home Page

We change to use the post list page as the home page We modify the application config-uration as follows

return array(

rsquodefaultControllerrsquo=gtrsquopostrsquo

)

Tip Because PostController already declares list to be its default action whenwe access the home page of the application we will see the result generated by thelist action of the post controller

632 Enabling Schema Caching

Because ActiveRecord relies on the metadata about tables to determine the column infor-mation it takes time to read the metadata and analyze it This may not be a problemduring development stage but for an application running in production mode it is a totalwaste of time if the database schema does not change Therefore we should enable theschema caching by modifying the application configuration as follows

50 6 Final Work

return array(

rsquocomponentsrsquo=gtarray(

rsquocachersquo=gtarray(

rsquoclassrsquo=gtrsquoCDbCachersquo

)

rsquodbrsquo=gtarray(

rsquoclassrsquo=gtrsquosystemdbCDbConnectionrsquo

rsquoconnectionStringrsquo=gtrsquosqlitewwwrootblogprotecteddatablogdbrsquo

rsquoschemaCachingDurationrsquo=gt3600

)

)

)

In the above we first add a cache component which uses a default SQLite database as thecaching storage If our server is equipped with other caching extensions such as APCwe could change to use them as well We also modify the db component by setting itsschemaCachingDuration property to be 3600 which means the parsed database schemadata can remain valid in cache for 3600 seconds

633 Disabling Debugging Mode

We modify the entry script file wwwrootblogindexphp by removing the line definingthe constant YII DEBUG This constant is useful during development stage because it allowsYii to display more debugging information when an error occurs However when theapplication is running in production mode displaying debugging information is not agood idea because it may contain sensitive information such as where the script file islocated and the content in the file etc

634 Deploying the Application

The final deployment process manly involves copying the directory wwwrootblog to thetarget directory The following checklist shows every needed step

1 Install Yii in the target place if it is not available

2 Copy the entire directory wwwrootblog to the target place

3 Edit the entry script file indexphp by pointing the $yii variable to the new Yiibootstrap file

4 Edit the file protectedyiicphp by setting the $yiic variable to be the new Yiiyiicphp file

64 Future Enhancements 51

5 Change the permission of the directories assets and protectedruntime so that theyare writable by the Web server process

64 Future Enhancements

641 Using a Theme

Without writing any code our blog application is already themeable To use a themewe mainly need to develop the theme by writing customized view files in the theme Forexample to use a theme named classic that uses a different page layout we would createa layout view file wwwrootblogthemesclassicviewslayoutsmainphp We also needto change the application configuration to indicate our choice of the classic theme

return array(

rsquothemersquo=gtrsquoclassicrsquo

)

642 Internationalization

We may also internationalize our blog application so that its pages can be displayed indifferent languages This mainly involves efforts in two aspects

First we may create view files in different languages For example for the list pageof PostController we can create a view file wwwrootblogprotectedviewspostzh cn

listphp When the application is configured to use simplified Chinese (the language codeis zh cn) Yii will automatically use this new view file instead of the original one

Second we may create message translations for those messages generated by code Themessage translations should be saved as files under the directory wwwrootblogprotected

messages We also need to modify the code where we use text strings by enclosing themin the method call Yiit()

For more details about internationalization please refer to the Guide

643 Improving Performance with Cache

While the Yii framework itself is very efficient it is not necessarily true that an applicationwritten in Yii efficient There are several places in our blog application that we can improvethe performance For example the tag clould portlet could be one of the performancebottlenecks because it involves complex database query and PHP logic

52 6 Final Work

We can make use of the sophisticated caching feature provided by Yii to improve theperformance One of the most useful components in Yii is COutputCache which caches afragment of page display so that the underlying code generating the fragment does not needto be executed for every request For example in the layout file wwwrootblogprotected

viewslayoutsmainphp we can enclose the tag cloud portlet with COutputCache

ltphp if($this-gtbeginCache(rsquotagCloudrsquo array(rsquodurationrsquo=gt3600))) gt

ltphp $this-gtwidget(rsquoTagCloudrsquo array(

rsquomaxTagsrsquo=gtYiiapp()-gtparams[rsquotagCloudCountrsquo]

)) gt

ltphp $this-gtendCache() gt

With the above code the tag cloud display will be served from cache instead of beinggenerated on-the-fly for every request The cached content will remain valid in cache for3600 seconds

644 Adding New Features

Our blog application only has very basic functionalities To become a complete blogsystem more features are needed for example calendar portlet email notifications postcategorization archived post portlet and so on We will leave the implementation of thesefeatures to interested readers

  • Contents
  • License
  • Getting Started
    • Building a Blog System using Yii
    • Testdriving with Yii
      • Installing Yii
      • Creating Skeleton Application
      • Application Workflow
        • Requirements Analysis
        • Overall Design
          • Initial Prototyping
            • Setting Up Database
              • Creating Database
              • Establishing Database Connection
                • Scaffolding
                • Authenticating User
                • Summary
                  • Post Management
                    • Customizing Post Model
                      • Customizing rules() Method
                      • Customizing relations() Method
                      • Representing Status in Text
                        • Creating and Updating Posts
                          • Customizing Access Control
                          • Customizing create and update Operations
                            • Displaying Posts
                              • Customizing view Operation
                              • Customizing index Operation
                                • Managing Posts
                                  • Listing Posts in Tabular View
                                  • Deleting Posts
                                      • Comment Management
                                        • Customizing Comment Model
                                          • Customizing rules() Method
                                          • Customizing attributeLabels() Method
                                          • Customizing Saving Process
                                            • Creating and Displaying Comments
                                              • Displaying Comments
                                              • Creating Comments
                                                • Managing Comments
                                                  • Updating and Deleting Comments
                                                  • Approving Comments
                                                      • Portlets
                                                        • Customizing Page Layout
                                                        • Creating User Menu Portlet
                                                          • Creating UserMenu Class
                                                          • Creating userMenu View
                                                          • Using UserMenu Portlet
                                                          • Testing UserMenu Portlet
                                                          • Summary
                                                            • Creating Tag Cloud Portlet
                                                              • Creating TagCloud Class
                                                              • Using TagCloud Portlet
                                                                • Creating Recent Comments Portlet
                                                                  • Creating RecentComments Class
                                                                  • Creating recentComments View
                                                                  • Using RecentComments Portlet
                                                                      • Final Work
                                                                        • Beautifying URLs
                                                                        • Logging Errors
                                                                        • Final Tune-up and Deployment
                                                                          • Changing Home Page
                                                                          • Enabling Schema Caching
                                                                          • Disabling Debugging Mode
                                                                          • Deploying the Application
                                                                            • Future Enhancements
                                                                              • Using a Theme
                                                                              • Internationalization
                                                                              • Improving Performance with Cache
                                                                              • Adding New Features
Page 55: Yii Blog 1.1.0 Demo Tutor

48 6 Final Work

rsquopostltidd+gtlttitlegtrsquo=gtrsquopostviewrsquorsquopostslttaggtrsquo=gtrsquopostindexrsquo

)

)

)

)

In the above we configure the urlManager component by setting its urlFormat propertyto be path and adding a set of rules

The rules are used by urlManager to parse and create the URLs in the desired formatFor example the first rule says that if a URL indexphppostsyii is requested theurlManager component should be responsible to dispatch the request to the route post

index and generate a tag GET parameter with the value yii On the other hand whencreating a URL with the route postindex and parameter tag the urlManager componentwill also use this rule to generate the desired URL indexphppostsyii For this reasonwe say that urlManager is a two-way URL manager

The urlManager component can further beautify our URLs such as hiding indexphp inthe URLs appending suffix like html to the URLs We can obtain these features easilyby configuring various properties of urlManager in the application configuration For moredetails please refer to the Guide

62 Logging Errors

A production Web application often needs sophisticated logging for various events In ourblog application we would like to log the errors occurring when it is being used Sucherrors could be programming mistakes or usersrsquo misuse of the system Logging these errorswill help us to improve the blog application

We enable the error logging by modifying the application configuration as follows

return array(

rsquopreloadrsquo=gtarray(rsquologrsquo)

rsquocomponentsrsquo=gtarray(

rsquologrsquo=gtarray(

rsquoclassrsquo=gtrsquoCLogRouterrsquo

rsquoroutesrsquo=gtarray(

array(

rsquoclassrsquo=gtrsquoCFileLogRoutersquo

rsquolevelsrsquo=gtrsquoerror warningrsquo

63 Final Tune-up and Deployment 49

)

)

)

)

)

With the above configuration if an error or warning occurs detailed information will belogged and saved in a file located under the directory wwwrootblogprotectedruntime

The log component offers more advanced features such as sending log messages to a listof email addresses displaying log messages in JavaScript console window etc For moredetails please refer to the Guide

63 Final Tune-up and Deployment

We are close to finish our blog application Before deployment we would like to do sometune-ups

631 Changing Home Page

We change to use the post list page as the home page We modify the application config-uration as follows

return array(

rsquodefaultControllerrsquo=gtrsquopostrsquo

)

Tip Because PostController already declares list to be its default action whenwe access the home page of the application we will see the result generated by thelist action of the post controller

632 Enabling Schema Caching

Because ActiveRecord relies on the metadata about tables to determine the column infor-mation it takes time to read the metadata and analyze it This may not be a problemduring development stage but for an application running in production mode it is a totalwaste of time if the database schema does not change Therefore we should enable theschema caching by modifying the application configuration as follows

50 6 Final Work

return array(

rsquocomponentsrsquo=gtarray(

rsquocachersquo=gtarray(

rsquoclassrsquo=gtrsquoCDbCachersquo

)

rsquodbrsquo=gtarray(

rsquoclassrsquo=gtrsquosystemdbCDbConnectionrsquo

rsquoconnectionStringrsquo=gtrsquosqlitewwwrootblogprotecteddatablogdbrsquo

rsquoschemaCachingDurationrsquo=gt3600

)

)

)

In the above we first add a cache component which uses a default SQLite database as thecaching storage If our server is equipped with other caching extensions such as APCwe could change to use them as well We also modify the db component by setting itsschemaCachingDuration property to be 3600 which means the parsed database schemadata can remain valid in cache for 3600 seconds

633 Disabling Debugging Mode

We modify the entry script file wwwrootblogindexphp by removing the line definingthe constant YII DEBUG This constant is useful during development stage because it allowsYii to display more debugging information when an error occurs However when theapplication is running in production mode displaying debugging information is not agood idea because it may contain sensitive information such as where the script file islocated and the content in the file etc

634 Deploying the Application

The final deployment process manly involves copying the directory wwwrootblog to thetarget directory The following checklist shows every needed step

1 Install Yii in the target place if it is not available

2 Copy the entire directory wwwrootblog to the target place

3 Edit the entry script file indexphp by pointing the $yii variable to the new Yiibootstrap file

4 Edit the file protectedyiicphp by setting the $yiic variable to be the new Yiiyiicphp file

64 Future Enhancements 51

5 Change the permission of the directories assets and protectedruntime so that theyare writable by the Web server process

64 Future Enhancements

641 Using a Theme

Without writing any code our blog application is already themeable To use a themewe mainly need to develop the theme by writing customized view files in the theme Forexample to use a theme named classic that uses a different page layout we would createa layout view file wwwrootblogthemesclassicviewslayoutsmainphp We also needto change the application configuration to indicate our choice of the classic theme

return array(

rsquothemersquo=gtrsquoclassicrsquo

)

642 Internationalization

We may also internationalize our blog application so that its pages can be displayed indifferent languages This mainly involves efforts in two aspects

First we may create view files in different languages For example for the list pageof PostController we can create a view file wwwrootblogprotectedviewspostzh cn

listphp When the application is configured to use simplified Chinese (the language codeis zh cn) Yii will automatically use this new view file instead of the original one

Second we may create message translations for those messages generated by code Themessage translations should be saved as files under the directory wwwrootblogprotected

messages We also need to modify the code where we use text strings by enclosing themin the method call Yiit()

For more details about internationalization please refer to the Guide

643 Improving Performance with Cache

While the Yii framework itself is very efficient it is not necessarily true that an applicationwritten in Yii efficient There are several places in our blog application that we can improvethe performance For example the tag clould portlet could be one of the performancebottlenecks because it involves complex database query and PHP logic

52 6 Final Work

We can make use of the sophisticated caching feature provided by Yii to improve theperformance One of the most useful components in Yii is COutputCache which caches afragment of page display so that the underlying code generating the fragment does not needto be executed for every request For example in the layout file wwwrootblogprotected

viewslayoutsmainphp we can enclose the tag cloud portlet with COutputCache

ltphp if($this-gtbeginCache(rsquotagCloudrsquo array(rsquodurationrsquo=gt3600))) gt

ltphp $this-gtwidget(rsquoTagCloudrsquo array(

rsquomaxTagsrsquo=gtYiiapp()-gtparams[rsquotagCloudCountrsquo]

)) gt

ltphp $this-gtendCache() gt

With the above code the tag cloud display will be served from cache instead of beinggenerated on-the-fly for every request The cached content will remain valid in cache for3600 seconds

644 Adding New Features

Our blog application only has very basic functionalities To become a complete blogsystem more features are needed for example calendar portlet email notifications postcategorization archived post portlet and so on We will leave the implementation of thesefeatures to interested readers

  • Contents
  • License
  • Getting Started
    • Building a Blog System using Yii
    • Testdriving with Yii
      • Installing Yii
      • Creating Skeleton Application
      • Application Workflow
        • Requirements Analysis
        • Overall Design
          • Initial Prototyping
            • Setting Up Database
              • Creating Database
              • Establishing Database Connection
                • Scaffolding
                • Authenticating User
                • Summary
                  • Post Management
                    • Customizing Post Model
                      • Customizing rules() Method
                      • Customizing relations() Method
                      • Representing Status in Text
                        • Creating and Updating Posts
                          • Customizing Access Control
                          • Customizing create and update Operations
                            • Displaying Posts
                              • Customizing view Operation
                              • Customizing index Operation
                                • Managing Posts
                                  • Listing Posts in Tabular View
                                  • Deleting Posts
                                      • Comment Management
                                        • Customizing Comment Model
                                          • Customizing rules() Method
                                          • Customizing attributeLabels() Method
                                          • Customizing Saving Process
                                            • Creating and Displaying Comments
                                              • Displaying Comments
                                              • Creating Comments
                                                • Managing Comments
                                                  • Updating and Deleting Comments
                                                  • Approving Comments
                                                      • Portlets
                                                        • Customizing Page Layout
                                                        • Creating User Menu Portlet
                                                          • Creating UserMenu Class
                                                          • Creating userMenu View
                                                          • Using UserMenu Portlet
                                                          • Testing UserMenu Portlet
                                                          • Summary
                                                            • Creating Tag Cloud Portlet
                                                              • Creating TagCloud Class
                                                              • Using TagCloud Portlet
                                                                • Creating Recent Comments Portlet
                                                                  • Creating RecentComments Class
                                                                  • Creating recentComments View
                                                                  • Using RecentComments Portlet
                                                                      • Final Work
                                                                        • Beautifying URLs
                                                                        • Logging Errors
                                                                        • Final Tune-up and Deployment
                                                                          • Changing Home Page
                                                                          • Enabling Schema Caching
                                                                          • Disabling Debugging Mode
                                                                          • Deploying the Application
                                                                            • Future Enhancements
                                                                              • Using a Theme
                                                                              • Internationalization
                                                                              • Improving Performance with Cache
                                                                              • Adding New Features
Page 56: Yii Blog 1.1.0 Demo Tutor

63 Final Tune-up and Deployment 49

)

)

)

)

)

With the above configuration if an error or warning occurs detailed information will belogged and saved in a file located under the directory wwwrootblogprotectedruntime

The log component offers more advanced features such as sending log messages to a listof email addresses displaying log messages in JavaScript console window etc For moredetails please refer to the Guide

63 Final Tune-up and Deployment

We are close to finish our blog application Before deployment we would like to do sometune-ups

631 Changing Home Page

We change to use the post list page as the home page We modify the application config-uration as follows

return array(

rsquodefaultControllerrsquo=gtrsquopostrsquo

)

Tip Because PostController already declares list to be its default action whenwe access the home page of the application we will see the result generated by thelist action of the post controller

632 Enabling Schema Caching

Because ActiveRecord relies on the metadata about tables to determine the column infor-mation it takes time to read the metadata and analyze it This may not be a problemduring development stage but for an application running in production mode it is a totalwaste of time if the database schema does not change Therefore we should enable theschema caching by modifying the application configuration as follows

50 6 Final Work

return array(

rsquocomponentsrsquo=gtarray(

rsquocachersquo=gtarray(

rsquoclassrsquo=gtrsquoCDbCachersquo

)

rsquodbrsquo=gtarray(

rsquoclassrsquo=gtrsquosystemdbCDbConnectionrsquo

rsquoconnectionStringrsquo=gtrsquosqlitewwwrootblogprotecteddatablogdbrsquo

rsquoschemaCachingDurationrsquo=gt3600

)

)

)

In the above we first add a cache component which uses a default SQLite database as thecaching storage If our server is equipped with other caching extensions such as APCwe could change to use them as well We also modify the db component by setting itsschemaCachingDuration property to be 3600 which means the parsed database schemadata can remain valid in cache for 3600 seconds

633 Disabling Debugging Mode

We modify the entry script file wwwrootblogindexphp by removing the line definingthe constant YII DEBUG This constant is useful during development stage because it allowsYii to display more debugging information when an error occurs However when theapplication is running in production mode displaying debugging information is not agood idea because it may contain sensitive information such as where the script file islocated and the content in the file etc

634 Deploying the Application

The final deployment process manly involves copying the directory wwwrootblog to thetarget directory The following checklist shows every needed step

1 Install Yii in the target place if it is not available

2 Copy the entire directory wwwrootblog to the target place

3 Edit the entry script file indexphp by pointing the $yii variable to the new Yiibootstrap file

4 Edit the file protectedyiicphp by setting the $yiic variable to be the new Yiiyiicphp file

64 Future Enhancements 51

5 Change the permission of the directories assets and protectedruntime so that theyare writable by the Web server process

64 Future Enhancements

641 Using a Theme

Without writing any code our blog application is already themeable To use a themewe mainly need to develop the theme by writing customized view files in the theme Forexample to use a theme named classic that uses a different page layout we would createa layout view file wwwrootblogthemesclassicviewslayoutsmainphp We also needto change the application configuration to indicate our choice of the classic theme

return array(

rsquothemersquo=gtrsquoclassicrsquo

)

642 Internationalization

We may also internationalize our blog application so that its pages can be displayed indifferent languages This mainly involves efforts in two aspects

First we may create view files in different languages For example for the list pageof PostController we can create a view file wwwrootblogprotectedviewspostzh cn

listphp When the application is configured to use simplified Chinese (the language codeis zh cn) Yii will automatically use this new view file instead of the original one

Second we may create message translations for those messages generated by code Themessage translations should be saved as files under the directory wwwrootblogprotected

messages We also need to modify the code where we use text strings by enclosing themin the method call Yiit()

For more details about internationalization please refer to the Guide

643 Improving Performance with Cache

While the Yii framework itself is very efficient it is not necessarily true that an applicationwritten in Yii efficient There are several places in our blog application that we can improvethe performance For example the tag clould portlet could be one of the performancebottlenecks because it involves complex database query and PHP logic

52 6 Final Work

We can make use of the sophisticated caching feature provided by Yii to improve theperformance One of the most useful components in Yii is COutputCache which caches afragment of page display so that the underlying code generating the fragment does not needto be executed for every request For example in the layout file wwwrootblogprotected

viewslayoutsmainphp we can enclose the tag cloud portlet with COutputCache

ltphp if($this-gtbeginCache(rsquotagCloudrsquo array(rsquodurationrsquo=gt3600))) gt

ltphp $this-gtwidget(rsquoTagCloudrsquo array(

rsquomaxTagsrsquo=gtYiiapp()-gtparams[rsquotagCloudCountrsquo]

)) gt

ltphp $this-gtendCache() gt

With the above code the tag cloud display will be served from cache instead of beinggenerated on-the-fly for every request The cached content will remain valid in cache for3600 seconds

644 Adding New Features

Our blog application only has very basic functionalities To become a complete blogsystem more features are needed for example calendar portlet email notifications postcategorization archived post portlet and so on We will leave the implementation of thesefeatures to interested readers

  • Contents
  • License
  • Getting Started
    • Building a Blog System using Yii
    • Testdriving with Yii
      • Installing Yii
      • Creating Skeleton Application
      • Application Workflow
        • Requirements Analysis
        • Overall Design
          • Initial Prototyping
            • Setting Up Database
              • Creating Database
              • Establishing Database Connection
                • Scaffolding
                • Authenticating User
                • Summary
                  • Post Management
                    • Customizing Post Model
                      • Customizing rules() Method
                      • Customizing relations() Method
                      • Representing Status in Text
                        • Creating and Updating Posts
                          • Customizing Access Control
                          • Customizing create and update Operations
                            • Displaying Posts
                              • Customizing view Operation
                              • Customizing index Operation
                                • Managing Posts
                                  • Listing Posts in Tabular View
                                  • Deleting Posts
                                      • Comment Management
                                        • Customizing Comment Model
                                          • Customizing rules() Method
                                          • Customizing attributeLabels() Method
                                          • Customizing Saving Process
                                            • Creating and Displaying Comments
                                              • Displaying Comments
                                              • Creating Comments
                                                • Managing Comments
                                                  • Updating and Deleting Comments
                                                  • Approving Comments
                                                      • Portlets
                                                        • Customizing Page Layout
                                                        • Creating User Menu Portlet
                                                          • Creating UserMenu Class
                                                          • Creating userMenu View
                                                          • Using UserMenu Portlet
                                                          • Testing UserMenu Portlet
                                                          • Summary
                                                            • Creating Tag Cloud Portlet
                                                              • Creating TagCloud Class
                                                              • Using TagCloud Portlet
                                                                • Creating Recent Comments Portlet
                                                                  • Creating RecentComments Class
                                                                  • Creating recentComments View
                                                                  • Using RecentComments Portlet
                                                                      • Final Work
                                                                        • Beautifying URLs
                                                                        • Logging Errors
                                                                        • Final Tune-up and Deployment
                                                                          • Changing Home Page
                                                                          • Enabling Schema Caching
                                                                          • Disabling Debugging Mode
                                                                          • Deploying the Application
                                                                            • Future Enhancements
                                                                              • Using a Theme
                                                                              • Internationalization
                                                                              • Improving Performance with Cache
                                                                              • Adding New Features
Page 57: Yii Blog 1.1.0 Demo Tutor

50 6 Final Work

return array(

rsquocomponentsrsquo=gtarray(

rsquocachersquo=gtarray(

rsquoclassrsquo=gtrsquoCDbCachersquo

)

rsquodbrsquo=gtarray(

rsquoclassrsquo=gtrsquosystemdbCDbConnectionrsquo

rsquoconnectionStringrsquo=gtrsquosqlitewwwrootblogprotecteddatablogdbrsquo

rsquoschemaCachingDurationrsquo=gt3600

)

)

)

In the above we first add a cache component which uses a default SQLite database as thecaching storage If our server is equipped with other caching extensions such as APCwe could change to use them as well We also modify the db component by setting itsschemaCachingDuration property to be 3600 which means the parsed database schemadata can remain valid in cache for 3600 seconds

633 Disabling Debugging Mode

We modify the entry script file wwwrootblogindexphp by removing the line definingthe constant YII DEBUG This constant is useful during development stage because it allowsYii to display more debugging information when an error occurs However when theapplication is running in production mode displaying debugging information is not agood idea because it may contain sensitive information such as where the script file islocated and the content in the file etc

634 Deploying the Application

The final deployment process manly involves copying the directory wwwrootblog to thetarget directory The following checklist shows every needed step

1 Install Yii in the target place if it is not available

2 Copy the entire directory wwwrootblog to the target place

3 Edit the entry script file indexphp by pointing the $yii variable to the new Yiibootstrap file

4 Edit the file protectedyiicphp by setting the $yiic variable to be the new Yiiyiicphp file

64 Future Enhancements 51

5 Change the permission of the directories assets and protectedruntime so that theyare writable by the Web server process

64 Future Enhancements

641 Using a Theme

Without writing any code our blog application is already themeable To use a themewe mainly need to develop the theme by writing customized view files in the theme Forexample to use a theme named classic that uses a different page layout we would createa layout view file wwwrootblogthemesclassicviewslayoutsmainphp We also needto change the application configuration to indicate our choice of the classic theme

return array(

rsquothemersquo=gtrsquoclassicrsquo

)

642 Internationalization

We may also internationalize our blog application so that its pages can be displayed indifferent languages This mainly involves efforts in two aspects

First we may create view files in different languages For example for the list pageof PostController we can create a view file wwwrootblogprotectedviewspostzh cn

listphp When the application is configured to use simplified Chinese (the language codeis zh cn) Yii will automatically use this new view file instead of the original one

Second we may create message translations for those messages generated by code Themessage translations should be saved as files under the directory wwwrootblogprotected

messages We also need to modify the code where we use text strings by enclosing themin the method call Yiit()

For more details about internationalization please refer to the Guide

643 Improving Performance with Cache

While the Yii framework itself is very efficient it is not necessarily true that an applicationwritten in Yii efficient There are several places in our blog application that we can improvethe performance For example the tag clould portlet could be one of the performancebottlenecks because it involves complex database query and PHP logic

52 6 Final Work

We can make use of the sophisticated caching feature provided by Yii to improve theperformance One of the most useful components in Yii is COutputCache which caches afragment of page display so that the underlying code generating the fragment does not needto be executed for every request For example in the layout file wwwrootblogprotected

viewslayoutsmainphp we can enclose the tag cloud portlet with COutputCache

ltphp if($this-gtbeginCache(rsquotagCloudrsquo array(rsquodurationrsquo=gt3600))) gt

ltphp $this-gtwidget(rsquoTagCloudrsquo array(

rsquomaxTagsrsquo=gtYiiapp()-gtparams[rsquotagCloudCountrsquo]

)) gt

ltphp $this-gtendCache() gt

With the above code the tag cloud display will be served from cache instead of beinggenerated on-the-fly for every request The cached content will remain valid in cache for3600 seconds

644 Adding New Features

Our blog application only has very basic functionalities To become a complete blogsystem more features are needed for example calendar portlet email notifications postcategorization archived post portlet and so on We will leave the implementation of thesefeatures to interested readers

  • Contents
  • License
  • Getting Started
    • Building a Blog System using Yii
    • Testdriving with Yii
      • Installing Yii
      • Creating Skeleton Application
      • Application Workflow
        • Requirements Analysis
        • Overall Design
          • Initial Prototyping
            • Setting Up Database
              • Creating Database
              • Establishing Database Connection
                • Scaffolding
                • Authenticating User
                • Summary
                  • Post Management
                    • Customizing Post Model
                      • Customizing rules() Method
                      • Customizing relations() Method
                      • Representing Status in Text
                        • Creating and Updating Posts
                          • Customizing Access Control
                          • Customizing create and update Operations
                            • Displaying Posts
                              • Customizing view Operation
                              • Customizing index Operation
                                • Managing Posts
                                  • Listing Posts in Tabular View
                                  • Deleting Posts
                                      • Comment Management
                                        • Customizing Comment Model
                                          • Customizing rules() Method
                                          • Customizing attributeLabels() Method
                                          • Customizing Saving Process
                                            • Creating and Displaying Comments
                                              • Displaying Comments
                                              • Creating Comments
                                                • Managing Comments
                                                  • Updating and Deleting Comments
                                                  • Approving Comments
                                                      • Portlets
                                                        • Customizing Page Layout
                                                        • Creating User Menu Portlet
                                                          • Creating UserMenu Class
                                                          • Creating userMenu View
                                                          • Using UserMenu Portlet
                                                          • Testing UserMenu Portlet
                                                          • Summary
                                                            • Creating Tag Cloud Portlet
                                                              • Creating TagCloud Class
                                                              • Using TagCloud Portlet
                                                                • Creating Recent Comments Portlet
                                                                  • Creating RecentComments Class
                                                                  • Creating recentComments View
                                                                  • Using RecentComments Portlet
                                                                      • Final Work
                                                                        • Beautifying URLs
                                                                        • Logging Errors
                                                                        • Final Tune-up and Deployment
                                                                          • Changing Home Page
                                                                          • Enabling Schema Caching
                                                                          • Disabling Debugging Mode
                                                                          • Deploying the Application
                                                                            • Future Enhancements
                                                                              • Using a Theme
                                                                              • Internationalization
                                                                              • Improving Performance with Cache
                                                                              • Adding New Features
Page 58: Yii Blog 1.1.0 Demo Tutor

64 Future Enhancements 51

5 Change the permission of the directories assets and protectedruntime so that theyare writable by the Web server process

64 Future Enhancements

641 Using a Theme

Without writing any code our blog application is already themeable To use a themewe mainly need to develop the theme by writing customized view files in the theme Forexample to use a theme named classic that uses a different page layout we would createa layout view file wwwrootblogthemesclassicviewslayoutsmainphp We also needto change the application configuration to indicate our choice of the classic theme

return array(

rsquothemersquo=gtrsquoclassicrsquo

)

642 Internationalization

We may also internationalize our blog application so that its pages can be displayed indifferent languages This mainly involves efforts in two aspects

First we may create view files in different languages For example for the list pageof PostController we can create a view file wwwrootblogprotectedviewspostzh cn

listphp When the application is configured to use simplified Chinese (the language codeis zh cn) Yii will automatically use this new view file instead of the original one

Second we may create message translations for those messages generated by code Themessage translations should be saved as files under the directory wwwrootblogprotected

messages We also need to modify the code where we use text strings by enclosing themin the method call Yiit()

For more details about internationalization please refer to the Guide

643 Improving Performance with Cache

While the Yii framework itself is very efficient it is not necessarily true that an applicationwritten in Yii efficient There are several places in our blog application that we can improvethe performance For example the tag clould portlet could be one of the performancebottlenecks because it involves complex database query and PHP logic

52 6 Final Work

We can make use of the sophisticated caching feature provided by Yii to improve theperformance One of the most useful components in Yii is COutputCache which caches afragment of page display so that the underlying code generating the fragment does not needto be executed for every request For example in the layout file wwwrootblogprotected

viewslayoutsmainphp we can enclose the tag cloud portlet with COutputCache

ltphp if($this-gtbeginCache(rsquotagCloudrsquo array(rsquodurationrsquo=gt3600))) gt

ltphp $this-gtwidget(rsquoTagCloudrsquo array(

rsquomaxTagsrsquo=gtYiiapp()-gtparams[rsquotagCloudCountrsquo]

)) gt

ltphp $this-gtendCache() gt

With the above code the tag cloud display will be served from cache instead of beinggenerated on-the-fly for every request The cached content will remain valid in cache for3600 seconds

644 Adding New Features

Our blog application only has very basic functionalities To become a complete blogsystem more features are needed for example calendar portlet email notifications postcategorization archived post portlet and so on We will leave the implementation of thesefeatures to interested readers

  • Contents
  • License
  • Getting Started
    • Building a Blog System using Yii
    • Testdriving with Yii
      • Installing Yii
      • Creating Skeleton Application
      • Application Workflow
        • Requirements Analysis
        • Overall Design
          • Initial Prototyping
            • Setting Up Database
              • Creating Database
              • Establishing Database Connection
                • Scaffolding
                • Authenticating User
                • Summary
                  • Post Management
                    • Customizing Post Model
                      • Customizing rules() Method
                      • Customizing relations() Method
                      • Representing Status in Text
                        • Creating and Updating Posts
                          • Customizing Access Control
                          • Customizing create and update Operations
                            • Displaying Posts
                              • Customizing view Operation
                              • Customizing index Operation
                                • Managing Posts
                                  • Listing Posts in Tabular View
                                  • Deleting Posts
                                      • Comment Management
                                        • Customizing Comment Model
                                          • Customizing rules() Method
                                          • Customizing attributeLabels() Method
                                          • Customizing Saving Process
                                            • Creating and Displaying Comments
                                              • Displaying Comments
                                              • Creating Comments
                                                • Managing Comments
                                                  • Updating and Deleting Comments
                                                  • Approving Comments
                                                      • Portlets
                                                        • Customizing Page Layout
                                                        • Creating User Menu Portlet
                                                          • Creating UserMenu Class
                                                          • Creating userMenu View
                                                          • Using UserMenu Portlet
                                                          • Testing UserMenu Portlet
                                                          • Summary
                                                            • Creating Tag Cloud Portlet
                                                              • Creating TagCloud Class
                                                              • Using TagCloud Portlet
                                                                • Creating Recent Comments Portlet
                                                                  • Creating RecentComments Class
                                                                  • Creating recentComments View
                                                                  • Using RecentComments Portlet
                                                                      • Final Work
                                                                        • Beautifying URLs
                                                                        • Logging Errors
                                                                        • Final Tune-up and Deployment
                                                                          • Changing Home Page
                                                                          • Enabling Schema Caching
                                                                          • Disabling Debugging Mode
                                                                          • Deploying the Application
                                                                            • Future Enhancements
                                                                              • Using a Theme
                                                                              • Internationalization
                                                                              • Improving Performance with Cache
                                                                              • Adding New Features
Page 59: Yii Blog 1.1.0 Demo Tutor

52 6 Final Work

We can make use of the sophisticated caching feature provided by Yii to improve theperformance One of the most useful components in Yii is COutputCache which caches afragment of page display so that the underlying code generating the fragment does not needto be executed for every request For example in the layout file wwwrootblogprotected

viewslayoutsmainphp we can enclose the tag cloud portlet with COutputCache

ltphp if($this-gtbeginCache(rsquotagCloudrsquo array(rsquodurationrsquo=gt3600))) gt

ltphp $this-gtwidget(rsquoTagCloudrsquo array(

rsquomaxTagsrsquo=gtYiiapp()-gtparams[rsquotagCloudCountrsquo]

)) gt

ltphp $this-gtendCache() gt

With the above code the tag cloud display will be served from cache instead of beinggenerated on-the-fly for every request The cached content will remain valid in cache for3600 seconds

644 Adding New Features

Our blog application only has very basic functionalities To become a complete blogsystem more features are needed for example calendar portlet email notifications postcategorization archived post portlet and so on We will leave the implementation of thesefeatures to interested readers

  • Contents
  • License
  • Getting Started
    • Building a Blog System using Yii
    • Testdriving with Yii
      • Installing Yii
      • Creating Skeleton Application
      • Application Workflow
        • Requirements Analysis
        • Overall Design
          • Initial Prototyping
            • Setting Up Database
              • Creating Database
              • Establishing Database Connection
                • Scaffolding
                • Authenticating User
                • Summary
                  • Post Management
                    • Customizing Post Model
                      • Customizing rules() Method
                      • Customizing relations() Method
                      • Representing Status in Text
                        • Creating and Updating Posts
                          • Customizing Access Control
                          • Customizing create and update Operations
                            • Displaying Posts
                              • Customizing view Operation
                              • Customizing index Operation
                                • Managing Posts
                                  • Listing Posts in Tabular View
                                  • Deleting Posts
                                      • Comment Management
                                        • Customizing Comment Model
                                          • Customizing rules() Method
                                          • Customizing attributeLabels() Method
                                          • Customizing Saving Process
                                            • Creating and Displaying Comments
                                              • Displaying Comments
                                              • Creating Comments
                                                • Managing Comments
                                                  • Updating and Deleting Comments
                                                  • Approving Comments
                                                      • Portlets
                                                        • Customizing Page Layout
                                                        • Creating User Menu Portlet
                                                          • Creating UserMenu Class
                                                          • Creating userMenu View
                                                          • Using UserMenu Portlet
                                                          • Testing UserMenu Portlet
                                                          • Summary
                                                            • Creating Tag Cloud Portlet
                                                              • Creating TagCloud Class
                                                              • Using TagCloud Portlet
                                                                • Creating Recent Comments Portlet
                                                                  • Creating RecentComments Class
                                                                  • Creating recentComments View
                                                                  • Using RecentComments Portlet
                                                                      • Final Work
                                                                        • Beautifying URLs
                                                                        • Logging Errors
                                                                        • Final Tune-up and Deployment
                                                                          • Changing Home Page
                                                                          • Enabling Schema Caching
                                                                          • Disabling Debugging Mode
                                                                          • Deploying the Application
                                                                            • Future Enhancements
                                                                              • Using a Theme
                                                                              • Internationalization
                                                                              • Improving Performance with Cache
                                                                              • Adding New Features