Yii快速入门(五)扩展

在开发中扩展Yii是一个很常见的行为.例如,当你写一个新的控制器时,你通过继承 CController 类扩展了 Yii;当你编写一个新的组件时,你正在继承 CWidget 或者一个已存在的组件类.如果扩展代码是由第三方开发者为了复用而设计的,我们则称之为 extension(扩展)。
一个扩展通常是为了一个单一的目的服务的.在 Yii 中,他可以按照如下分类:

 * 应用的部件
 * 组件
 * 控制器
 * 动作
 * 过滤器
 * 控制台命令
 * 校验器: 校验器是一个继承自 CValidator 类的部件。
 * 辅助器: 辅助器是一个只具有静态方法的类.它类似于使用类名作为命名空间的全局函数。
 * 模块: 模块是一个有着若干个类文件和相应特长文件的包.一个模块通常更高级,比一个单一的部件具备更先进的功能.
         例如我们可以拥有一个具备整套用户管理功能的模块。

扩展也可以是不属于上述分类中的任何一个的部件。事实上,Yii 是设计得很谨慎的,以至于几乎它的每段代码都可以被扩展和订制以适用于特定需求。

一、使用扩展

使用扩展通常包含了以下三步:

  1. 从 Yii 的 扩展库 下载扩展。
  2. 解压到 应用程序的基目录 的子目录 extensions/xyz 下,这里的 xyz 是扩展的名称。
  3. 导入, 配置和使用扩展。
    每个扩展都有一个所有扩展中唯一的名称标识。把一个扩展命名为 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::initCWidget::run。第一种方法是当我们在视图中使用 $this->beginWidget 插入一个小工具时被调用,第二种方法在$this->endWidget被调用时调用。如果我们想在这两个方法调用之间捕捉和处理显示的内容,我们可以开始output bufferingCWidget::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::preFilterCFilter::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));
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,602评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,442评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,878评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,306评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,330评论 5 373
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,071评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,382评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,006评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,512评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,965评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,094评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,732评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,283评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,286评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,512评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,536评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,828评论 2 345

推荐阅读更多精彩内容

  • 一、入口文件 入口文件内容:一般格式如下: 二、主配置文件 保存位置:你的应用/protected/config/...
    layjoy阅读 1,198评论 4 14
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,515评论 25 707
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,600评论 18 139
  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,587评论 18 399
  • 任何创作艺术作品的人,只需拥有1000名铁杆粉丝,也就是无论你创造出什么作品,他/她都愿意付费购买的粉丝,便能糊口...
    托爸阅读 209评论 0 0