在开发中扩展Yii是一个很常见的行为.例如,当你写一个新的控制器时,你通过继承 CController 类扩展了 Yii;当你编写一个新的组件时,你正在继承 CWidget 或者一个已存在的组件类.如果扩展代码是由第三方开发者为了复用而设计的,我们则称之为 extension(扩展)。
一个扩展通常是为了一个单一的目的服务的.在 Yii 中,他可以按照如下分类:
* 应用的部件
* 组件
* 控制器
* 动作
* 过滤器
* 控制台命令
* 校验器: 校验器是一个继承自 CValidator 类的部件。
* 辅助器: 辅助器是一个只具有静态方法的类.它类似于使用类名作为命名空间的全局函数。
* 模块: 模块是一个有着若干个类文件和相应特长文件的包.一个模块通常更高级,比一个单一的部件具备更先进的功能.
例如我们可以拥有一个具备整套用户管理功能的模块。
扩展也可以是不属于上述分类中的任何一个的部件。事实上,Yii 是设计得很谨慎的,以至于几乎它的每段代码都可以被扩展和订制以适用于特定需求。
一、使用扩展
使用扩展通常包含了以下三步:
- 从 Yii 的 扩展库 下载扩展。
- 解压到 应用程序的基目录 的子目录 extensions/xyz 下,这里的 xyz 是扩展的名称。
- 导入, 配置和使用扩展。
每个扩展都有一个所有扩展中唯一的名称标识。把一个扩展命名为 xyz ,我们也可以使用路径别名定位到包含了 xyz 所有文件的基目录。
不同的扩展有着不同的导入,配置,使用要求.以下是我们通常会用到扩展的场景,按照他们在 概述 中的描述分类。
1、应用的部件
使用 应用的部件, 首先我们需要添加一个新条目到 应用配置 的 components 属性, 如下所示:
return array(
// 'preload'=>array('xyz',...),
'components'=>array(
'xyz'=>array(
'class'=>'application.extensions.xyz.XyzClass',
'property1'=>'value1',
'property2'=>'value2',
),
// 其他部件配置
),
);
然后,我们可以在任何地方通过使用 Yii::app()->xyz
来访问部件.部件将会被 惰性创建(就是,仅当它第一次被访问时创建.) , 除非我们把它配置到 preload
属性里。
2、组件
组件 主要用在 视图 里.假设组件类 XyzClass 属于 xyz 扩展,我们可以如下在视图中使用它:
// 组件不需要主体内容
<?php $this->widget('application.extensions.xyz.XyzClass', array(
'property1'=>'value1',
'property2'=>'value2')); ?>
// 组件可以包含主体内容
<?php $this->beginWidget('application.extensions.xyz.XyzClass', array(
'property1'=>'value1',
'property2'=>'value2')); ?>
...组件的主体内容...
<?php $this->endWidget(); ?>
3、动作
动作 被 控制器 用于响应指定的用户请求.假设动作的类 XyzClass
属于 xyz
扩展,我们可以在我们的控制器类里重写 CController::actions
方法来使用它:
class TestController extends CController
{
public function actions()
{
return array(
'xyz'=>array(
'class'=>'application.extensions.xyz.XyzClass',
'property1'=>'value1',
'property2'=>'value2',
),
// 其他动作
);
}
}
然后,我们可以通过 路由 test/xyz 来访问。
4、过滤器
过滤器 也被 控制器 使用。过滤器主要用于当其被 动作 挂起时预处理,提交处理用户请求。假设过滤器的类 XyzClass 属于 xyz 扩展,我们可以在我们的控制器类里重写 CController::filters
方法来使用它:
class TestController extends CController
{
public function filters()
{
return array(
array(
'application.extensions.xyz.XyzClass',
'property1'=>'value1',
'property2'=>'value2',
),
// 其他过滤器
);
}
}
在上述代码中,我们可以在数组的第一个元素离使用加号或者减号操作符来限定过滤器只在那些动作中生效。更多信息,请参照文档的 CController
。
5、控制器
控制器 提供了一套可以被用户请求的动作。我们需要在 应用配置 里设置 CWebApplication::controllerMap
属性,才能在控制器里使用扩展:
return array(
'controllerMap'=>array(
'xyz'=>array(
'class'=>'application.extensions.xyz.XyzClass',
'property1'=>'value1',
'property2'=>'value2',
),
// 其他控制器
),
);
然后, 一个在控制里的 a 行为就可以通过 路由 xyz/a 来访问了。
6、校验器
校验器主要用在 模型类 (继承自 CFormModel 或者 CActiveRecord) 中.假设校验器类 XyzClass 属于 xyz 扩展,我们可以在我们的模型类中通过 CModel::rules
重写 CModel::rules
来使用它:
class MyModel extends CActiveRecord // or CFormModel
{
public function rules()
{
return array(
array(
'attr1, attr2',
'application.extensions.xyz.XyzClass',
'property1'=>'value1',
'property2'=>'value2',
),
// 其他校验规则
);
}
}
7、控制台命令
控制台命令扩展通常使用一个额外的命令来增强 yiic 的功能.假设命令控制台 XyzClass 属于 xyz 扩展,我们可以通过设定控制台应用的配置来使用它:
return array(
'commandMap'=>array(
'xyz'=>array(
'class'=>'application.extensions.xyz.XyzClass',
'property1'=>'value1',
'property2'=>'value2',
),
// 其他命令
),
);
然后,我们就能使用配备了额外命令 xyz 的 yiic 工具了。
注意: 控制台应用通常使用了一个不同于 Web 应用的配置文件.如果使用了 yiic webapp
命令创建了一个应用,这样的话,控制台应用的 protected/yiic 的配置文件就是 protected/config/console.php 了,而Web应用的配置文件 则是 protected/config/main.php。
8、模块
模块通常由多个类文件组成,且往往综合上述扩展类型。因此,你应该按照和以下一致的指令来使用模块。
9、通用部件
使用一个通用 部件, 我们首先需要通过使用
Yii::import('application.extensions.xyz.XyzClass');
来包含它的类文件。然后,我们既可以创建一个类的实例,配置它的属性,也可以调用它的方法。我们还可以创建一个新的子类来扩展它。
二、创建扩展
由于扩展意味着是第三方开发者使用,需要一些额外的努力去创建它。以下是一些一般性的指导原则:
*扩展最好是自己自足。也就是说,其外部的依赖应是最少的。如果用户的扩展需要安装额外的软件包,类或资源档案,
这将是一个头疼的问题。
*文件属于同一个扩展的,应组织在同一目录下,目录名用扩展名称。
*扩展里面的类应使用一些单词字母前缀,以避免与其他扩展命名冲突。
*扩展应该提供详细的安装和API文档。这将减少其他开发员使用扩展时花费的时间和精力。
*扩展应该用适当的许可。如果您想您的扩展能在开源和闭源项目中使用,你可以考虑使用许可证,
如BSD的,麻省理工学院等,但不是GPL的,因为它要求其衍生的代码是开源的。
在下面,我们根据 overview
中所描述的分类,描述如何创建一个新的扩展。当您要创建一个主要用于在您自己项目的component
部件,这些描述也适用。
1、Application Component(应用部件)
一个application component
应实现接口IApplicationComponent
或继承CApplicationComponent
。主要需要实现的方法是 IApplicationComponent::init
,部件在此执行一些初始化工作。此方法在部件创建和属性值(在application configuration里指定的 )被赋值后调用。
默认情况下,一个应用程序部件创建和初始化,只有当它首次访问期间要求处理。如果一个应用程序部件需要在应用程序实例被创建后创建,它应要求用户在CApplication::preload
的属性中列出他的编号。
2、Widget(小工具)
widget
应继承CWidget
或其子类。 A widget should extend from CWidget or its child classes.
最简单的方式建立一个新的小工具是继承一个现成的小工具和重载它的方法或改变其默认的属性值。例如,如果您想为CTabView使用更好的CSS样式,您可以配置其CTabView::cssFile属性,当使用的小工具时。您还可以继承CTabView如下,让您在使用小工具时,不再需要配置属性。
class MyTabView extends CTabView
{
public function init()
{
if($this->cssFile===null)
{
$file=dirname(__FILE__).DIRECTORY_SEPARATOR.'tabview.css';
$this->cssFile=Yii::app()->getAssetManager()->publish($file);
}
parent::init();
}
}
在上面的,我们重载CWidget::init
方法和指定CTabView::cssFile
的 URL到我们的新的默认CSS样式如果此属性未设置时。我们把新的CSS样式文件和MyTabView类文件放在相同的目录下,以便他们能够封装成扩展。由于CSS样式文件不是通过Web访问,我们需要发布作为一项asset资源。
要从零开始创建一个新的小工具,我们主要是需要实现两个方法:CWidget::init
和CWidget::run
。第一种方法是当我们在视图中使用 $this->beginWidget
插入一个小工具时被调用,第二种方法在$this->endWidget
被调用时调用。如果我们想在这两个方法调用之间捕捉和处理显示的内容,我们可以开始output buffering
在CWidget::init
和在CWidget::run
中回收缓冲输出作进一步处理。 If we want to capture and process the content displayed between these two method invocations, we can start output buffering in CWidget::init and retrieve the buffered output in CWidget::run for further processing.
在网页中使用的小工具,小工具往往包括CSS,Javascript或其他资源文件。我们叫这些文件assets
,因为他们和小工具类在一起,而且通常Web用户无法访问。为了使这些档案通过Web访问,我们需要用CWebApplication::assetManager
发布他们,例如上述代码段所示。此外,如果我们想包括CSS或JavaScript文件在当前的网页,我们需要使用CClientScript
注册 :
class MyWidget extends CWidget
{
protected function registerClientScript()
{
// ...publish CSS or JavaScript file here...
$cs=Yii::app()->clientScript;
$cs->registerCssFile($cssFile);
$cs->registerScriptFile($jsFile);
}
}
小工具也可能有自己的视图文件。如果是这样,创建一个目录命名views在包括小工具类文件的目录下,并把所有的视图文件放里面。在小工具类中使用$this->render('ViewName')
来render渲染小工具视图,类似于我们在控制器里做。
3、Action(动作)
action应继承CAction或者其子类。action要实现的主要方法是IAction::run
。
4、Filter(过滤器)
filter应继承CFilter 或者其子类。filter要实现的主要方法是CFilter::preFilter
和CFilter::postFilter
。前者是在action之前被执行,而后者是在之后。
class MyFilter extends CFilter
{
protected function preFilter($filterChain)
{
// logic being applied before the action is executed
return true; // false if the action should not be executed
}
protected function postFilter($filterChain)
{
// logic being applied after the action is executed
}
}
参数$filterChain的类型是CFilterChain,其包含当前被filter的action的相关信息。
5、Controller(控制器)
controller要作为扩展需继承CExtController
,而不是 CController
。主要的原因是因为CController
认定控制器视图文件位于application.views.ControllerID 下,而CExtController认定视图文件在views目录下,也是包含控制器类目录的一个子目录。因此,很容易重新分配控制器,因为它的视图文件和控制类是在一起的。
6、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);
}
}
7、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';
}
}
8、Module(模块)
请参阅modules一节中关于就如何创建一个模块。
一般准则制订一个模块,它应该是独立的。模块所使用的资源文件(如CSS , JavaScript ,图片),应该和模块一起分发。还有模块应发布它们,以便可以Web访问它们 。
9、Generic Component(通用组件)
开发一个通用组件扩展类似写一个类。还有,该组件还应该自足,以便它可以很容易地被其他开发者使用。
三、使用第三方库
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));