Controller(控制器)
controller要作为扩展需继承CExtController,而不是 CController。主要的原因是因为CController 认定控制器视图文件位于application.views.ControllerID 下,而CExtController认定视图文件在views目录下,也是包含控制器类目录的一个子目录。因此,很容易重新分配控制器,因为它的视图文件和控制类是在一起的。
Validator(验证)
Validator需继承CValidator和实现CValidator::validateAttribute方法。
class MyValidator extends CValidator
{
protected function validateAttribute($model,$attribute)
{
$value=$model->$attribute;
if($value has error)
$model->addError($attribute,$errorMessage);
}
}
Console Command(控制台命令)
console command 应继承CConsoleCommand和实现CConsoleCommand::run方法。 或者,我们可以重载CConsoleCommand::getHelp来提供一些更好的有关帮助命令。
class MyCommand extends CConsoleCommand
{
public function run($args)
{
// $args gives an array of the command-line arguments for this command
}
public function getHelp()
{
return 'Usage: how to use this command';
}
}
Module(模块)
请参阅modules一节中关于就如何创建一个模块。
一般准则制订一个模块,它应该是独立的。模块所使用的资源文件(如CSS , JavaScript ,图片),应该和模块一起分发。还有模块应发布它们,以便可以Web访问它们 。
Generic Component(通用组件)
开发一个通用组件扩展类似写一个类。还有,该组件还应该自足,以便它可以很容易地被其他开发者使用。
Using 3rd-Party Libraries(使用第三方库)
Yii是精心设计,使第三方库可易于集成,进一步扩大Yii的功能。 当在一个项目中使用第三方库,程序员往往遇到关于类命名和文件包含的问题。 因为所有Yii类以C字母开头,这就减少可能会出现的类命名问题;而且因为Yii依赖SPL autoload执行类文件包含,如果他们使用相同的自动加载功能或PHP包含路径包含类文件,它可以很好地结合。
下面我们用一个例子来说明如何在一个Yii application从Zend framework使用Zend_Search_Lucene部件。
首先,假设protected是application base directory,我们提取Zend Framework的发布文件到protected/vendors目录 。 确认protected/vendors/Zend/Search/Lucene.php文件存在。
第二,在一个controller类文件的开始,加入以下行:
Yii::import('application.vendors.*');
require_once('Zend/Search/Lucene.php');
上述代码包含类文件Lucene.php。因为我们使用的是相对路径,我们需要改变PHP的包含路径,以使文件可以正确定位。这是通过在require_once之前调用Yii::import做到。
一旦上述设立准备就绪后,我们可以在controller action里使用Lucene类,类似如下:
$lucene=new Zend_Search_Lucene($pathOfIndex);
$hits=$lucene->find(strtolower($keyword));
URL Management(网址管理)
Web应用程序完整的URL管理包括两个方面。首先, 当用户请求约定的URL,应用程序需要解析它变成可以理解的参数。第二,应用程序需求提供一种创造URL的方法,以便创建的URL应用程序可以理解的。对于Yii应用程序,这些通过CUrlManager辅助完成。
Creating URLs(创建网址)
虽然URL可被硬编码在控制器的视图(view)文件,但往往可以很灵活地动态创建它们:
$url=$this->createUrl($route,$params);
$this指的是控制器实例; $route指定请求的route 的要求;$params 列出了附加在网址中的GET参数。
默认情况下,URL以get格式使用createUrl 创建。例如,提供$route='post/read'和$params=array('id'=>100) ,我们将获得以下网址:
/index.php?r=post/read&id=100
参数以一系列Name=Value通过符号串联起来出现在请求字符串,r参数指的是请求的route 。这种URL格式用户友好性不是很好,因为它需要一些非字字符。
我们可以使上述网址看起来更简洁,更不言自明,通过采用所谓的'path格式,省去查询字符串和把GET参数加到路径信息,作为网址的一部分:
/index.php/post/read/id/100
要更改URL格式,我们应该配置urlManager应用元件,以便createUrl可以自动切换到新格式和应用程序可以正确理解新的网址:
array(
......
'components'=>array(
......
'urlManager'=>array(
'urlFormat'=>'path',
),
),
);
请注意,我们不需要指定的urlManager元件的类,因为它在CWebApplication预声明为CUrlManager。
createurl方法所产生的是一个相对地址。为了得到一个绝对的url ,我们可以用前缀yii">
提示:此网址通过createurl方法所产生的是一个相对地址。为了得到一个绝对的url ,我们可以用前缀yii: :app()->hostInfo ,或调用createAbsoluteUrl 。
User-friendly URLs(用户友好的URL)
当用path格式URL,我们可以指定某些URL规则使我们的网址更用户友好性。例如,我们可以产生一个短短的URL/post/100 ,而不是冗长/index.php/post/read/id/100。网址创建和解析都是通过CUrlManager指定网址规则。
要指定的URL规则,我们必须设定urlManager 应用元件的属性rules:
array(
......
'components'=>array(
......
'urlManager'=>array(
'urlFormat'=>'path',
'rules'=>array(
'pattern1'=>'route1',
'pattern2'=>'route2',
'pattern3'=>'route3',
),
),
),
);
这些规则以一系列的路线格式对数组指定,每对对应于一个单一的规则。路线(route)的格式必须是有效的正则表达式,没有分隔符和修饰语。它是用于匹配网址的路径信息部分。还有route应指向一个有效的路线控制器。
规则可以绑定少量的GET参数。这些出现在规则格式的GET参数,以一种特殊令牌格式表现如下:
<ParamName:ParamPattern>
ParamName表示GET参数名字,可选项ParamPattern表示将用于匹配GET参数值的正则表达式。当生成一个网址(URL)时,这些参数令牌将被相应的参数值替换;当解析一个网址时,相应的GET参数将通过解析结果来生成。
我们使用一些例子来解释网址工作规则。我们假设我们的规则包括如下三个:
array(
'posts'=>'post/list',
'post/<id:\d+>'=>'post/read',
'post/<year:\d{4}>/<title>'=>'post/read',
)
· 调用$this->createUrl('post/list')生成/index.php/posts。第一个规则适用。
· 调用$this->createUrl('post/read',array('id'=>100))生成/index.php/post/100。第二个规则适用。
· 调用$this->createUrl('post/read',array('year'=>2008,'title'=>'a sample post'))生成/index.php/post/2008/a%20sample%20post。第三个规则适用。
· 调用$this->createUrl('post/read')产生/index.php/post/read。请注意,没有规则适用。
总之,当使用createUrl生成网址,路线和传递给该方法的GET参数被用来决定哪些网址规则适用。如果关联规则中的每个参数可以在GET参数找到的,将被传递给createUrl ,如果路线的规则也匹配路线参数,规则将用来生成网址。
如果GET参数传递到createUrl是以上所要求的一项规则,其他参数将出现在查询字符串。例如,如果我们调用$this->createUrl('post/read',array('id'=>100,'year'=>2008)) ,我们将获得/index.php/post/100?year=2008。为了使这些额外参数出现在路径信息的一部分,我们应该给规则附加/* 。 因此,该规则post/<id:\d+>/* ,我们可以获取网址/index.php/post/100/year/2008 。
正如我们提到的,URL规则的其他用途是解析请求网址。当然,这是URL生成的一个逆过程。例如, 当用户请求/index.php/post/100 ,上面例子的第二个规则将适用来解析路线post/read和GET参数array('id'=>100) (可通过$_GET获得) 。
createurl方法所产生的是一个相对地址。为了得到一个绝对的url ,我们可以用前缀yii">
注:使用的URL规则将降低应用的性能。这是因为当解析请求的URL , CUrlManager 尝试使用每个规则来匹配它,直到某个规则可以适用。因此,高流量网站应用应尽量减少其使用的URL规则。
隐藏 index.php
还有一点,我们可以做进一步清理我们的网址,即在URL中藏匿index.php入口脚本。这就要求我们配置Web服务器,以及urlManager应用程序元件。
我们首先需要配置Web服务器,这样一个URL没有入口脚本仍然可以处理入口脚本。如果是Apache HTTP server ,可以通过打开网址重写引擎和指定一些重写规则。这两个操作可以在包含入口脚本的目录下的.htaccess文件里实现。下面是一个示例:
Options +FollowSymLinks
IndexIgnore */*
RewriteEngine on
# if a directory or a file exists, use it directly
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
# otherwise forward it to index.php
RewriteRule . index.php
然后,我们设定urlManager元件的showScriptName属性为 false。
现在,如果我们调用$this->createUrl('post/read',array('id'=>100)) ,我们将获取网址/post/100 。更重要的是,这个URL可以被我们的Web应用程序正确解析。
Faking URL Suffix(伪造URL后缀)
我们还可以添加一些网址的后缀。例如,我们可以用/post/100.html来替代/post/100 。这使得它看起来更像一个静态网页URL。为了做到这一点,只需配置urlManager元件的urlSuffix属性为你所喜欢的后缀。
验证和授权(Authentication and Authorization)
如果网页的访问需要用户权限限制,那么我们需要使用验证(Authentication)和授权(Authorization)。验证是指核查某人表明的身份信息是否与系统相合。一般来说使用用户名和密码,当然也可能使用别的表明身份方式,录入智能卡,指纹等等。授权是找出已通过验证的用户是否允许操作特定的资源。一般的做法是找出此用户是否属于某个允许操作此资源的角色。
利用Yii内置的验证和授权(auth)框架,我们可以轻松实现上述功能。
Yii auth framework 的核心一块是一个事先声明的user application component(用户应用部件),实现IWebUser接口的对象。此用户部件代表当前用户存储的身份信息。我们能够通过Yii::app()->user在任何地方来获取它。
使用此用户部件,可以通过CWebUser::isGuest检查一个用户是否登陆。可以login (登陆)或者logout (注销)一个用户;可以通过CWebUser::checkAccess检查此用户是否能够执行特定的操作;还可以获得此用户的unique identifier(唯一标识)和别的身份信息。
定义身份类 (Defining Identity Class)
为了验证一个用户,我们定义一个有验证逻辑的身份类。这个身份类实现IUserIdentity 接口。不同的类可能实现不同的验证方式(例如:OpenID,LDAP)。最好是继承 CUserIdentity,此类是居于用户名和密码的验证方式。
定义身份类的主要工作是实现IUserIdentity::authenticate方法。在用户会话中根据需要,身份类可能需要定义别的身份信息
下面的例子,我们使用Active Record来验证提供的用户名、密码和数据库的用户表是否吻合。我们通过重写getId函数来返回验证过程中获得的_id变量(缺省的实现则是返回用户名)。在验证过程中,我们还借助CBaseUserIdentity::setState函数把获得的title信息存成一个状态。
class UserIdentity extends CUserIdentity
{
private $_id;
public function authenticate()
{
$record=User::model()->findByAttributes(array('username'=>$this->username));
if($record===null)
$this->errorCode=self::ERROR_USERNAME_INVALID;
else if($record->password!==md5($this->password))
$this->errorCode=self::ERROR_PASSWORD_INVALID;
else
{
$this->_id=$record->id;
$this->setState('title', $record->title);
$this->errorCode=self::ERROR_NONE;
}
return !$this->errorCode;
}
public function getId()
{
return $this->_id;
}
}
作为状态存储的信息(通过调用CBaseUserIdentity::setState)将被传递给CWebUser。而后者则把这些信息存放在一个永久存储媒介上(如session)。我们可以把这些信息当作CWebUser的属性来使用。例如,为了获得当前用户的title信息,我们可以使用Yii::app()->user->title(这项功能是在1.0.3版本引入的。在之前的版本里,我们需要使用Yii::app()->user->getState('title'))。
提示: 缺省情况下,CWebUser用session来存储用户身份信息。如果允许基于cookie方式登录(通过设置 CWebUser::allowAutoLogin为 true),用户身份信息将被存放在cookie中。确记敏感信息不要存放(例如 password) 。
登录和注销(Login and Logout)
使用身份类和用户部件,我们方便的实现登录和注销。
// 使用提供的用户名和密码登录用户
$identity=new UserIdentity($username,$password);
if($identity->authenticate())
Yii::app()->user->login($identity);
else
echo $identity->errorMessage;
......
// 注销当前用户
Yii::app()->user->logout();
缺省情况下,用户将根据session configuration完成一序列inactivity动作后注销。设置用户部件的allowAutoLogin属性为true和在CWebUser::login方法中设置一个持续时间参数来改变这个行为。即使用户关闭浏览器,此用户将保留用户登陆状态时间为被设置的持续时间之久。前提是用户的浏览器接受cookies。
// 保留用户登陆状态时间7天
// 确保用户部件的allowAutoLogin被设置为true。
Yii::app()->user->login($identity,3600*24*7);
访问控制过滤器(Access Control Filter)
访问控制过滤器是检查当前用户是否能执行访问的controller action的初步授权模式。这种授权模式基于用户名,客户IP地址和访问类型。 It is provided as a filter named as "accessControl".
小贴士: 访问控制过滤器适用于简单的验证。需要复杂的访问控制,需要使用将要讲解到的基于角色访问控制(role-based access (RBAC)).
在控制器(controller)里重载CController::filters方法设置访问过滤器来控制访问动作(看 Filter 了解更多过滤器设置信息)。
class PostController extends CController
{
......
public function filters()
{
return array(
'accessControl',
);
}
}
在上面,设置的access control过滤器将应用于PostController里每个动作。过滤器具体的授权规则通过重载控制器的CController::accessRules方法来指定。
class PostController extends CController
{
......
public function accessRules()
{
return array(
array('deny',
'actions'=>array('create', 'edit'),
'users'=>array('?'),
),
array('allow',
'actions'=>array('delete'),
'roles'=>array('admin'),
),
array('deny',
'actions'=>array('delete'),
'users'=>array('*'),
),
);
}
}
上面设定了三个规则,每个用个数组表示。数组的第一个元素不是'allow'就是'deny',其他的是名-值成对形式设置规则参数的。上面的规则这样理解:create和edit动作不能被匿名执行;delete动作可以被admin角色的用户执行;delete动作不能被任何人执行。
访问规则是一个一个按照设定的顺序一个一个来执行判断的。和当前判断模式(例如:用户名、角色、客户端IP、地址)相匹配的第一条规则决定授权的结果。如果这个规则是allow,则动作可执行;如果是deny,不能执行;如果没有规则匹配,动作可以执行。
info|提示:为了确保某类动作在没允许情况下不被执行,设置一个匹配所有人的deny规则在最后,类似如下:
return array(
// ... 别的规则...
// 以下匹配所有人规则拒绝'delete'动作
array('deny',
'action'=>'delete',
),
);
因为如果没有设置规则匹配动作,动作缺省会被执行。
访问规则通过如下的上下文参数设置:
· actions: 设置哪个动作匹配此规则。
· users: 设置哪个用户匹配此规则。此当前用户的name 被用来匹配. 三种设定字符在这里可以用:
o *: 任何用户,包括匿名和验证通过的用户。
o ?: 匿名用户。
o @: 验证通过的用户。
· roles: 设定哪个角色匹配此规则。这里用到了将在后面描述的role-based access control技术。In particular, the rule is applied if CWebUser::checkAccess returns true for one of the roles.提示,用户角色应该被设置成allow规则,因为角色代表能做某些事情。
· ips: 设定哪个客户端IP匹配此规则。
· verbs: 设定哪种请求类型(例如:GET, POST)匹配此规则。
· expression: 设定一个PHP表达式。它的值用来表明这条规则是否适用。在表达式,你可以使用一个叫$user的变量,它代表的是Yii::app()->user。这个选项是在1.0.3版本里引入的。
授权处理结果(Handling Authorization Result)
当授权失败,即,用户不允许执行此动作,以下的两种可能将会产生:
· 如果用户没有登录和在用户部件中配置了loginUrl,浏览器将重定位网页到此配置URL。
· 否则一个错误代码401的HTTP例外将显示。
当配置loginUrl 属性,可以用相对和绝对URL。还可以使用数组通过CWebApplication::createUrl来生成URL。第一个元素将设置route 为登录控制器动作,其他为名-值成对形式的GET参数。如下,
array(
......
'components'=>array(
'user'=>array(
// 这实际上是默认值
'loginUrl'=>array('site/login'),
),
),
)
如果浏览器重定位到登录页面,而且登录成功,我们将重定位浏览器到引起验证失败的页面。我们怎么知道这个值呢?我们可以通过用户部件的returnUrl 属性获得。我们因此可以用如下执行重定向:
Yii::app()->request->redirect(Yii::app()->user->returnUrl);
基于角色的访问控制(Role-Based Access Control)
基于角色的访问控制( RBAC的)提供了一种简单而强大集中访问控制。请参阅维基文章 (http://en.wikipedia.org/wiki/Role-based_access_control)了解更多详细的RBAC与其他较传统的访问控制模式的比较。
Yii实现通过其authManager 应用程序组件分级RBAC的模式。在下面,我们首先介绍用于这模式的主要概念;我们然后描述了如何设定授权数据;最后,我们看看如何利用授权数据,以进行访问检查。
概览(Overview)
在Yii的RBAC的一个基本概念是authorization item(授权项目)。一个授权项目是一个做某事的许可(如创造新的博客发布,管理用户)。根据其粒度和targeted audience, 授权项目可分为operations(行动),tasks(任务)和 roles(角色)。角色包括任务,任务包括行动,行动是许可是个原子。 例如,我们就可以有一个administrator角色,包括post management和user management任务。user management 任务可能包括create user,update user和delete user行动。为了更灵活,Yii也可以允许角色包括其他角色和动作,任务包括其他任务,行动包括其他行动。
授权项目通过名称唯一确定。
授权项目可能与business rule(业务规则)关联。业务规则是一块PHP代码,将在检查访问此项的相关时被执行。只有当执行返回true,用户将被视为有权限此项所代表的许可。举例来说,当定义一项行动updatePost,我们想添加业务规则来检查,用户ID是否和帖子作者ID一样,以便只有作者自己能够有权限更新发布。
使用授权的项目,我们可以建立一个authorization hierarchy(授权等级)。在授权等级中如果项目A包括项目B,A是B的父亲(或说A继承B所代表的权限)。一个项目可以有多个子项目,也可以有多个父项目。因此,授权等级是一个partial-order图,而不是树型。在此等级中,角色项在最高,行动项在最底,任务项在中间。
一旦有了授权等级,我们可以在此等级中分配角色给应用用户。一个用户,一旦被分配了角色,将有角色所代表的权限。例如,如果我们指定 administrator角色给用户,他将拥有管理员权限其中包括post management和user management (和相应的操作,如create user) 。
现在精彩的部分开始。在控制器的行动,我们要检查,当前用户是否可以删除指定的发布。利用RBAC等级和分配,可以很容易做到这一点。如下:
if(Yii::app()->user->checkAccess('deletePost'))
{
// 删除此发布
}
配置授权管理器(Configuring Authorization Manager)
在我们准备定义授权等级和执行访问检查前,我们需要配置 authManager 应用程序组件。Yii 提供两种类型的授权管理器: CPhpAuthManager 和 CDbAuthManager 。前者使用的PHP脚本文件来存储授权数据,而后者的数据存储在数据库授权。当我们配置authManager应用部件,我们需要指定哪些部件类和部件的初始值。例如,
return array(
'components'=>array(
'db'=>array(
'class'=>'CDbConnection',
'connectionString'=>'sqlite:path/to/file.db',
),
'authManager'=>array(
'class'=>'CDbAuthManager',
'connectionID'=>'db',
),
),
);
然后,我们便可使用Yii::app()->authManager访问authManager应用部件。
定义授权等级(Defining Authorization Hierarchy)
定义授权等级涉及三个步骤:定义授权项目,建立授权项目关系项目,并分配角色给应用用户。authManager 应用部件提供了一整套的API来完成这些任务。
根据不同种类的项目调用下列方法之一定义授权项目:
· CAuthManager::createRole
· CAuthManager::createTask
· CAuthManager::createOperation
一旦我们拥有一套授权项目,我们可以调用以下方法建立授权项目关系:
· CAuthManager::addItemChild
· CAuthManager::removeItemChild
· CAuthItem::addChild
· CAuthItem::removeChild
最后,我们调用下列方法来分配角色项目给各个用户:
· CAuthManager::assign
· CAuthManager::revoke
下面我们将展示一个例子是关于用所提供的API建立一个授权等级:
$auth=Yii::app()->authManager;
$auth->createOperation('createPost','create a post');
$auth->createOperation('readPost','read a post');
$auth->createOperation('updatePost','update a post');
$auth->createOperation('deletePost','delete a post');
$bizRule='return Yii::app()->user->id==$params["post"]->authID;';
$task=$auth->createTask('updateOwnPost','update a post by author himself',$bizRule);
$task->addChild('updatePost');
$role=$auth->createRole('reader');
$role->addChild('readPost');
$role=$auth->createRole('author');
$role->addChild('reader');
$role->addChild('createPost');
$role->addChild('updateOwnPost');
$role=$auth->createRole('editor');
$role->addChild('reader');
$role->addChild('updatePost');
$role=$auth->createRole('admin');
$role->addChild('editor');
$role->addChild('author');
$role->addChild('deletePost');
$auth->assign('reader','readerA');
$auth->assign('author','authorB');
$auth->assign('editor','editorC');
$auth->assign('admin','adminD');
请注意,我们给updateOwnPost任务关联一个业务规则。在这个业务规则,我们只是检查目前的用户ID是否和指定的帖子的作者ID一样。当执行访问检查时,发布信息在开发者提供$params数组中。
info|信息:虽然上面的例子看起来冗长和枯燥,这主要是为示范的目的。开发者通常需要制定一些用户接口,以便最终用户可以更直观使用它来建立一个授权等级。
访问检查(Access Checking)
为了执行访问检查,我们得先知道授权项目的名字。例如,如果要检查当前用户是否可以创建一个发布,我们将检查是否有createPost行动的权限。然后,我们调用 CWebUser : : checkAccess 执行访问检查:
if(Yii::app()->user->checkAccess('createPost'))
{
// 创建发布
}
如果授权规则关联了需要额外参数的商业规则,我们同样可以通过他们。例如,要检查如果用户是否可以更新发布,我们将编写
$params=array('post'=>$post);
if(Yii::app()->user->checkAccess('updateOwnPost',$params))
{
// 更新post
}
使用缺省角色(Default Roles)
注意: 缺省角色的功能是在1.0.3版本引入的。
很多Web应用需要一些很特殊的角色,它们通常需要被分配给几乎每一个用户。例如,我们可能需要为所有注册用户分配一些特殊的权力。假如要象上述方法那样去为每一个用户分配这种角色,我们在维护上将面临很多麻烦。因此,我们采用缺省角色功能来解决这个问题。
所谓缺省角色指的是被隐式分配给每一个用户(包括注册和非注册的用户)的角色。它们无需象前面所描述的那样去被分配给用户。当我们调用CWebUser::checkAccess,缺省角色将首先被检查,就像它们已经被分配给当前用户一样。
缺省角色必须通过CAuthManager::defaultRoles属性进行声明。例如,下面的应用配置声明了两个缺省角色:authenticated和guest。
return array(
'components'=>array(
'authManager'=>array(
'class'=>'CDbAuthManager',
'defaultRoles'=>array('authenticated', 'guest'),
),
),
);
因为缺省角色实质上是被分配给每一个用户的,它通常需要伴随一个业务规则用来确定它是否真正适用某个用户。例如,下面的代码定义了两个角色,authenticated和guest,它们在实质上分别被分配给已通过验证和未通过验证的用户。
$bizRule='return !Yii::app()->user->isGuest;';
$auth->createRole('authenticated',$bizRule);
$bizRule='return Yii::app()->user->isGuest;';
$auth->createRole('guest',$bizRule);
Theming(主题)
Theming是一个在Web应用程序里定制网页外观的系统方式。通过采用一个新的主题,网页应用程序的整体外观可以立即和戏剧性的改变。
在Yii,每个主题由一个目录代表,包含view文件,layout文件和相关的资源文件,如图片, CSS文件, JavaScript文件等。主题的名字就是他的目录名字。全部主题都放在在同一目录WebRoot/themes下 。在任何时候,只有一个主题可以被激活。
提示:默认的主题根目录WebRoot/themes可被配置成其他的。只需要配置themeManager应用部件的属性basePath和baseUrl为你所要的值。
要激活一个主题,设置Web应用程序的属性theme为你所要的名字。可以在application configuration中配置或者在执行过程中在控制器的动作里面修改。
注:主题名称是区分大小写的。如果您尝试启动一个不存在的主题, yii: :app()->theme将返回null 。
主题目录里面内容的组织方式和application base path目录下的组织方式一样。例如,所有的view文件必须位于views下 ,布局view文件在views/layouts下 ,和系统view文件在views/system下。例如,如果我们要替换PostController的create view文件为classic主题下,我们将保存新的view文件为WebRoot/themes/classic/views/post/create.php。
对于在module里面的控制器view文件,相应主题view文件将被放在views目录下。例如,如果上述的PostController是在一个命名为forum的模块里 ,我们应该保存create view 文件为WebRoot/themes/classic/views/forum/post/create.php 。如果 forum模块嵌套在另一个名为support模块里 ,那么view文件应为WebRoot/themes/classic/views/support/forum/post/create.php 。
注:由于views目录可能包含安全敏感数据,应当配置以防止被网络用户访问。
当我们调用render或renderPartial显示视图,相应的view文件以及布局文件将在当前激活的主题里寻找。如果发现,这些文件将被render渲染。否则,就后退到viewPath和layoutPath 所指定的预设位置寻找。
baseurl属性,我们就可以为此图像文件生成如下url,
yii">
提示:在一个主题的视图,我们经常需要链接其他主题资源文件。例如,我们可能要显示一个在主题下images目录里的图像文件。使用当前激活主题的baseurl属性,我们就可以为此图像文件生成如下url,
yii: :app()->theme->baseUrl . '/images/FileName.gif'
Logging
Yii provides a flexible and extensible logging feature. Messages logged can be classified according to log levels and message categories. Using level and category filters, selected messages can be further routed to different destinations, such as files, emails, browser windows, etc.
Message Logging
Messages can be logged by calling either Yii::log or Yii::trace. The difference between these two methods is that the latter logs a message only when the application is in debug mode.
Yii::log($message, $level, $category);
Yii::trace($message, $category);
When logging a message, we need to specify its category and level. Category is a string in the format of xxx.yyy.zzz which resembles to the path alias. For example, if a message is logged in CController, we may use the category system.web.CController. Message level should be one of the following values:
· trace: this is the level used by Yii::trace. It is for tracing the execution flow of the application during development.
· info: this is for logging general information.
· profile: this is for performance profile which is to be described shortly.
· warning: this is for warning messages.
· error: this is for fatal error messages.
Message Routing
Messages logged using Yii::log or Yii::trace are kept in memory. We usually need to display them in browser windows, or save them in some persistent storage such as files, emails. This is called message routing, i.e., sending messages to different destinations.
In Yii, message routing is managed by a CLogRouter application component. It manages a set of the so-called log routes. Each log route represents a single log destination. Messages sent along a log route can be filtered according to their levels and categories.
To use message routing, we need to install and preload a CLogRouter application component. We also need to configure its routes property with the log routes that we want. The following shows an example of the needed application configuration:
array(
......
'preload'=>array('log'),
'components'=>array(
......
'log'=>array(
'class'=>'CLogRouter',
'routes'=>array(
array(
'class'=>'CFileLogRoute',
'levels'=>'trace, info',
'categories'=>'system.*',
),
array(
'class'=>'CEmailLogRoute',
'levels'=>'error, warning',
'emails'=>'admin@example.com',
),
),
),
),
)
In the above example, we have two log routes. The first route is CFileLogRoute which saves messages in a file under the application runtime directory. Only messages whose level is trace or info and whose category starts with system. are saved. The second route is CEmailLogRoute which sends messages to the specified email addresses. Only messages whose level is error or warning are sent.
The following log routes are available in Yii:
· CDbLogRoute: saves messages in a database table.
· CEmailLogRoute: sends messages to specified email addresses.
· CFileLogRoute: saves messages in a file under the application runtime directory.
· CWebLogRoute: displays messages at the end of the current Web page.
· CProfileLogRoute: displays profiling messages at the end of the current Web page.
Info: Message routing occurs at the end of the current request cycle when the onEndRequest event is raised. To explicitly terminate the processing of the current request, call CApplication::end() instead of die() or exit(), because CApplication::end() will raise the onEndRequest event so that the messages can be properly logged.
Message Filtering
As we mentioned, messages can be filtered according to their levels and categories before they are sent long a log route. This is done by setting the levels and categories properties of the corresponding log route. Multiple levels or categories should be concatenated by commas.
Because message categories are in the format of xxx.yyy.zzz, we may treat them as a category hierarchy. In particular, we say xxx is the parent of xxx.yyy which is the parent of xxx.yyy.zzz. We can then use xxx.* to represent category xxx and all its child and grandchild categories.
Logging Context Information
Starting from version 1.0.6, we can specify to log additional context information, such as PHP predefined variables (e.g. $_GET, $_SERVER), session ID, user name, etc. This is accomplished by specifying the CLogRoute::filter property of a log route to be a suitable log filter.
The framework comes with the convenient CLogFilter that may be used as the needed log filter in most cases. By default, CLogFilter will log a message with variables like $_GET, $_SERVER which often contains valuable system context information. CLogFilter can also be configured to prefix each logged message with session ID, username, etc., which may greatly simplifying the global search when we are checking the numerous logged messages.
The following configuration shows how to enable logging context information. Note that each log route may have its own log filter. And by default, a log route does not have a log filter.
array(
......
'preload'=>array('log'),
'components'=>array(
......
'log'=>array(
'class'=>'CLogRouter',
'routes'=>array(
array(
'class'=>'CFileLogRoute',
'levels'=>'error',
'filter'=>'CLogFilter',
),
...other log routes...
),
),
),
)
Starting from version 1.0.7, Yii supports logging call stack information in the messages that are logged by calling Yii::trace. This feature is disabled by default because it lowers performance. To use this feature, simply define a constant named YII_TRACE_LEVEL at the beginning of the entry script (before including yii.php) to be an integer greater than 0. Yii will then append to every trace message with the file name and line number of the call stacks belonging to application code. The number YII_TRACE_LEVEL determines how many layers of each call stack should be recorded. This information is particularly useful during development stage as it can help us identify the places that trigger the trace messages.
Performance Profiling
Performance profiling is a special type of message logging. Performance profiling can be used to measure the time needed for the specified code blocks and find out what the performance bottleneck is.
To use performance profiling, we need to identify which code blocks need to be profiled. We mark the beginning and the end of each code block by inserting the following methods:
Yii::beginProfile('blockID');
...code block being profiled...
Yii::endProfile('blockID');
where blockID is an ID that uniquely identifies the code block.
Note, code blocks need to be nested properly. That is, a code block cannot intersect with another. It must be either at a parallel level or be completely enclosed by the other code block.
To show profiling result, we need to install a CLogRouter application component with a CProfileLogRoute log route. This is the same as we do with normal message routing. The CProfileLogRoute route will display the performance results at the end of the current page.
Profiling SQL Executions
Profiling is especially useful when working with database since SQL executions are often the main performance bottleneck of an application. While we can manually insert beginProfile and endProfile statements at appropriate places to measure the time spent in each SQL execution, starting from version 1.0.6, Yii provides a more systematic approach to solve this problem.
By setting CDbConnection::enableProfiling to be true in the application configuration, every SQL statement being executed will be profiled. The results can be readily displayed using the aforementioned CProfileLogRoute, which can show us how much time is spent in executing what SQL statement. We can also call CDbConnection::getStats() to retrieve the total number SQL statements executed and their total execution time.
Error Handling
Yii provides a complete error handling framework based on the PHP 5 exception mechanism. When the application is created to handle an incoming user request, it registers its handleError method to handle PHP warnings and notices; and it registers its handleException method to handle uncaught PHP exceptions. Consequently, if a PHP warning/notice or an uncaught exception occurs during the application execution, one of the error handlers will take over the control and start the necessary error handling procedure.
Tip: The registration of error handlers is done in the application's constructor by calling PHP functions set_exception_handler and set_error_handler. If you do not want Yii to handle the errors and exceptions, you may define constant YII_ENABLE_ERROR_HANDLER and YII_ENABLE_EXCEPTION_HANDLER to be false in the entry script.
By default, errorHandler (or exceptionHandler) will raise an onError event (or onException event). If the error (or exception) is not handled by any event handler, it will call for help from the errorHandler application component.
Raising Exceptions
Raising exceptions in Yii is not different from raising a normal PHP exception. One uses the following syntax to raise an exception when needed:
throw new ExceptionClass('ExceptionMessage');
Yii defines two exception classes: CException and CHttpException. The former is a generic exception class, while the latter represents an exception that should be displayed to end users. The latter also carries a statusCode property representing an HTTP status code. The class of an exception determines how it should be displayed, as we will explain next.
Tip: Raising a CHttpException exception is a simple way of reporting errors caused by user misoperation. For example, if the user provides an invalid post ID in the URL, we can simply do the following to show a 404 error (page not found):
// if post ID is invalid
throw new CHttpException(404,'The specified post cannot be found.');
Displaying Errors
When an error is forwarded to the CErrorHandler application component, it chooses an appropriate view to display the error. If the error is meant to be displayed to end users, such as a CHttpException, it will use a view named errorXXX, where XXX stands for the HTTP status code (e.g. 400, 404, 500). If the error is an internal one and should only be displayed to developers, it will use a view named exception. In the latter case, complete call stack as well as the error line information will be displayed.
Info: When the application runs in production mode, all errors including those internal ones will be displayed using view errorXXX. This is because the call stack of an error may contain sensitive information. In this case, developers should rely on the error logs to determine what is the real cause of an error.
CErrorHandler searches for the view file corresponding to a view in the following order:
1. WebRoot/themes/ThemeName/views/system: this is the system view directory under the currently active theme.
2. WebRoot/protected/views/system: this is the default system view directory for an application.
3. yii/framework/views: this is the standard system view directory provided by the Yii framework.
Therefore, if we want to customize the error display, we can simply create error view files under the system view directory of our application or theme. Each view file is a normal PHP script consisting of mainly HTML code. For more details, please refer to the default view files under the framework's view directory.
Handling Errors Using an Action
Starting from version 1.0.6, Yii allows using a controller action to handle the error display work. To do so, we should configure the error handler in the application configuration as follows:
return array(
......
'components'=>array(
'errorHandler'=>array(
'errorAction'=>'site/error',
),
),
);
In the above, we configure the CErrorHandler::errorAction property to be the route site/error which refers to the error action in SiteController. We may use a different route if needed.
We can write the error action like the following:
public function actionError()
{
if($error=Yii::app()->errorHandler->error)
$this->render('error', $error);
}
In the action, we first retrieve the detailed error information from CErrorHandler::error. If it is not empty, we render the error view together with the error information. The error information returned from CErrorHandler::error is an array with the following fields:
· code: the HTTP status code (e.g. 403, 500);
· type: the error type (e.g. CHttpException, PHP Error);
· message: the error message;
· file: the name of the PHP script file where the error occurs;
· line: the line number of the code where the error occurs;
· trace: the call stack of the error;
· source: the context source code where the error occurs.
Tip: The reason we check if CErrorHandler::error is empty or not is because the error action may be directly requested by an end user, in which case there is no error. Since we are passing the $error array to the view, it will be automatically expanded to individual variables. As a result, in the view we can access directly the variables such as $code, $type.
Message Logging
A message of level error will always be logged when an error occurs. If the error is caused by a PHP warning or notice, the message will be logged with category php; if the error is caused by an uncaught exception, the category would be exception.ExceptionClassName (for CHttpException its statusCode will also be appended to the category). One can thus exploit the logging feature to monitor errors happened during application execution.
Web Service
Web service 是一个软件系统,设计来支持计算机之间跨网络相互访问。在Web应用程序,它通常用一套API,可以被互联网访问和执行在远端系统主机上的被请求服务。系统主机所要求的服务。例如,以Flex为基础的客户端可能会援引函数实现在服务器端运行PHP的Web应用程序。 Web service依赖SOAP作为通信协议栈的基础层。
Yii提供CWebService和CWebServiceAction简化了在Web应用程序实现Web service。这些API以类形式实现,被称为service providers. Yii将为每个类产生一个WSDL,描述什么API有效和客户端怎么援引。当客户端援引API,Yii将实例化相应的service provider和调用被请求的API来完成请求。
注: CWebService 依靠PHP SOAP extension 。请确定您是否在试用本节中的例子前允许此扩展。
Defining Service Provider(定义Service Provider)
正如我们上文所述,service provider是一个类定义能被远程援引的方法。Yii依靠doc comment and class reflection识别哪些方法可以被远程调用和他们的参数还有返回值。
让我们以一个简单的股票报价服务开始。这项服务允许客户端请求指定股票的报价。我们确定service provider如下。请注意,我们定义扩展CController的提供类StockController。这是不是必需的。马上我们将解释为什么这样做。
class StockController extends CController
{
/**
* @param string the symbol of the stock
* @return float the stock price
* @soap
*/
public function getPrice($symbol)
{
$prices=array('IBM'=>100, 'GOOGLE'=>350);
return isset($prices[$symbol])?$prices[$symbol]:0;
//...return stock price for $symbol
}
}
在上面的,我们通过在文档注释中的@soap标签声明getPrice方法为一个Web service API。依靠文档注释指定输入的参数数据类型和返回值。其他的API可使用类似方式声明。
Declaring Web Service Action(定义Web Service动作)
已经定义了service provider,我们使他能够通过客户端访问。特别是,我们要创建一个控制器动作暴露这个服务。可以做到这一点很容易,在控制器类中定义一个CWebServiceAction动作。对于我们的例子中,我们把它放在StockController中。
class StockController extends CController
{
public function actions()
{
return array(
'quote'=>array(
'class'=>'CWebServiceAction',
),
);
}
/**
* @param string the symbol of the stock
* @return float the stock price
* @soap
*/
public function getPrice($symbol)
{
//...return stock price for $symbol
}
}
这就是我们需要建立的Web service!如果我们尝试访问动作网址http://hostname/path/to/index.php?r=stock/quote ,我们将看到很多XML内容,这实际上是我们定义的Web service的WSDL描述。
提示:在默认情况下, CWebServiceAction 假设当前的控制器 是service provider。这就是因为我们定义 getPrice方法在StockController中。
Consuming Web Service(消费Web Service)
要完成这个例子,让我们创建一个客户端来消费我们刚刚创建的Web service。例子中的客户端用php编写的,但可以用别的语言编写,例如Java, C#, Flex等等。
$client=new SoapClient('http://hostname/path/to/index.php?r=stock/quote');
echo $client->getPrice('GOOGLE');
在网页中或控制台模式运行以上脚本,我们将看到GOOGLE的价格350 。
延伸阅读:
Yii是什么