組件機制,是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);
}
}
經過以上例子,可以發現行為方法可以以組件普通方法直接運行。行為有何用處,我目前還沒有完全體會到其優勢。
<!--EndFragment-->yii的個人簡單理解
組件:Yii中幾乎所有可實例化並繼承自CComponent的類,均可稱為組件。
事件:類方法,供組件之間調用 已on開頭作為標識
行為:特殊的類,供組件直接拿來使用,類似trait特性