Yii的组件机制
组件机制,是Yii整个体系的思想精髓,在使用Yii之前,最应该先了解其组件机制,如果不了解这个机制,那么阅读Yii源代码会非常吃力。组件机制给Yii框架赋予了无穷的灵活性和可扩展性,可以毫不夸张地说,Yii框架的基础结构就是组件。大到CApplication对象、控制器、路由管理器(urlManager),小到一些其它插件,均是以组件形式存在的。
什么是Yii组件?
Yii中几乎所有可实例化并继承自CComponent的类,均可称为组件。
组件的特点是什么?
继承自CComponent类(直接继承或间接继承),拥有事件及行为机制,可在配置文件中定义其各个属性。
如何创建一个组件?
编写自定义类,并继承自CComponent类即可。
CComponent类是所有组件的基类,这个类在Yii框架中至关重要。具体来说,这个类主要实现以下三大功能:
1. 通过利用php的魔术方法__set, __get实现定义类的属性。也就是说一个组件的属性除了使用已经定义过的public成员属性,还可以利用CComponent实现的功能,通过扩展setXXX, getXXX方法扩展属性的设置和获取,对一些特殊的属性,我们可能希望在设置它之时就验证其是格式正确,此时就比较有用。
class webpage extends CComponent {
public $title;
private $_url;
public function setUrl($value='') {
if(is_url($value)){
$this->_url = $value;
}
}
public function getUrl() {
return $this->_url ;
}
}
$page = new webpage();
$page->title = "page title";
$page->url = "/index.php"; # call $page->seturl("/index.php");
echo $page->url #$page->geturl();
也就是说,如果一个组件定义了setXXX, getXXX,那么就可以在类外部使用普通的属性访问形式。
2. 同样利用setter, getter实现事件处理接口绑定。事件机制在Yii中也是无处不大,Yii使用大量的事件机制来实现组件之间的功能调用(观察者模式)。
那么,如何给组件定义一个事件呢?Yii规定onXX形式的方法,即称为事件,如以下定义:
class form extends CComponent {
public function onSubmit($event) {
$this->raiseEvent('onSubmit', $event);
}
}
使用以上固定代码,就给car组件创建了一个onStop事件。但是绑定事件究竟有何用处呢? 绑定事件的作用就是当组件产生一系列事件时,将自己的事件通过事件处理器,通知到绑定的各个对象上。例如我们希望当表单提交的时候,将此事件通知给日志组件让其记录之。
$form = new form();
$form ->attachEventHandler( 'onSubmit', array($logOjbect, "saveLog") );
$form->data = $_POST;
$form->onSubmit( new CEvent($form, array('data'=>$_POST) ) ); #激活事件执行,并调用事件处理接口logObject::saveLog
而logObject的代码可能如下:
class logObject {
public function saveLog($event) {
$event->sender === $form;
$event->params ;
}
}
显然这种方式远比传统方式调用更灵活,概念方式上也更先进一些。
另外,可以使用setter方法绑定事件:
$form->onSubmit = array($logOjbect, "saveLog") ;
注意:form类并未定义onSubmit成员属性。
同时,Yii实现了同一个事件绑定多个处理接口的机制,类似JavaScript中的addEventListener。
事件处理器接口柳芽,以php的callback类型格式为标准,详情参阅以下内容:http://php.net/manual/en/language.types.callable.php
如Yii的CLogRouter::init()中的代码:
Yii::app()->attachEventHandler('onEndRequest',array($this,'processLogs'));
当然也可以写成Yii::app()->onEndRequest = array($this,'processLogs');
而CApplication中定义了onEndRequest事件:
public function onEndRequest($event){
if(!$this->_ended){
$this->_ended=true;
$this->raiseEvent('onEndRequest',$event);
}
}
定义了事件,并给事件绑定了处理器还不够,还得在合适的地方激活事件,如CApplication::run()方法中的逻辑:
public function run(){
if($this->hasEventHandler('onBeginRequest'))
$this->onBeginRequest(new CEvent($this));
$this->processRequest();
if($this->hasEventHandler('onEndRequest'))
$this->onEndRequest(new CEvent($this));
}
也就说,要实现组件的事件机制,需要定义事件,绑定事件处理接口,激活事件。
行为机制
yii的行为机制,可以简单地认为就是一个组件将其它对象的方法或属性直接拿来使用(就如php 5.4引入的trait结构,与行为作用就类似)
trait SayWorld {
public function sayHello() {
echo 'hello world!';
}
}
class MyHelloWorld extends Base {
use SayWorld;
}
$o = new MyHelloWorld();
$o->sayHello();
输出 hello world!
一个行为,就是一个特殊的类,其定义了各种事件及其处理流程,我们先定义一个行为类,包含事件及其对应的方法。
class MyAppBehavior extends CBehavior {
public $status = "app behavior ended.";
public function events() {
return array(
'onEndRequest' => 'appEnd', #指定组件的onEndRequest事件发生后,调用行为的appEnd
);
}
public function appEnd($event = null) {
echo get_class($this);
}
}
$app->attachBehavior('myapp','MyAppBehavior');
$app->run();
echo $app->status ;
我们发现行为的方法及属性,都是可以被组件直接使用的。
另外一个常见的例子,我们经常要对用户输入的内容进行过滤处理,比如防止其输入html标签,这种情况下,我们也可以考虑使用行为机制处理之。
$form = new FormModel;
If($_POST) {
$form->attributes = $_POST ;
$form->attachBehavior('myFilter', array(
'class' => '',
'strip_tags' => true ,
));
$form->filter(new CEvent($form) ) ;
}
class myHtmlFilter extends CBehavior {
public $strip_tags = false ;
public function events() {
return array(
'filter' => 'filterHtml',
);
}
public function filterHtml($event) {
if($event->sender instanceof CFormModel) {
$input = $event->sender->attributes ;
$event->sender->attributes = $this->filter($input);
}
}
public function filter(&$data) {
return is_array($data)?array_map(array($this, 'filter'),$data):strip_tags($data);
}
}
经过以上例子,可以发现行为方法可以以组件普通方法直接运行。行为有何用处,我目前还没有完全体会到其优势。