# 事件
事件可以將自定義代碼“注入”到現有代碼中的特定執行點。附加自定義代碼到某個事件,當這個事件被觸發時,這些代碼就會自動執行。例如,郵件程序對象成功發出消息時可觸發?`messageSent`?事件。如想追蹤成功發送的消息,可以附加相應追蹤代碼到`messageSent`?事件。
Yii 引入了名為 yii\base\Component 的基類以支持事件。如果一個類需要觸發事件就應該繼承 yii\base\Component 或其子類。
## 事件處理器(Event Handlers)
事件處理器是一個[PHP 回調函數](http://www.php.net/manual/en/language.types.callable.php),當它所附加到的事件被觸發時它就會執行。可以使用以下回調函數之一:
* 字符串形式指定的 PHP 全局函數,如?`'trim'`?;
* 對象名和方法名數組形式指定的對象方法,如?`[$object, $method]`?;
* 類名和方法名數組形式指定的靜態類方法,如?`[$class, $method]`?;
* 匿名函數,如?`function ($event) { ... }`?。
事件處理器的格式是:
~~~
function ($event) {
// $event 是 yii\base\Event 或其子類的對象
}
~~~
通過?`$event`?參數,事件處理器就獲得了以下有關事件的信息:
* yii\base\Event::name:事件名
* yii\base\Event::sender:調用?`trigger()`?方法的對象
* yii\base\Event::data:附加事件處理器時傳入的數據,默認為空,后文詳述
## 附加事件處理器
調用 yii\base\Component::on() 方法來附加處理器到事件上。如:
~~~
$foo = new Foo;
// 處理器是全局函數
$foo->on(Foo::EVENT_HELLO, 'function_name');
// 處理器是對象方法
$foo->on(Foo::EVENT_HELLO, [$object, 'methodName']);
// 處理器是靜態類方法
$foo->on(Foo::EVENT_HELLO, ['app\components\Bar', 'methodName']);
// 處理器是匿名函數
$foo->on(Foo::EVENT_HELLO, function ($event) {
//事件處理邏輯
});
~~~
附加事件處理器時可以提供額外數據作為 yii\base\Component::on() 方法的第三個參數。數據在事件被觸發和處理器被調用時能被處理器使用。如:
~~~
// 當事件被觸發時以下代碼顯示 "abc"
// 因為 $event->data 包括被傳遞到 "on" 方法的數據
$foo->on(Foo::EVENT_HELLO, function ($event) {
echo $event->data;
}, 'abc');
~~~
## 事件處理器順序
可以附加一個或多個處理器到一個事件。當事件被觸發,已附加的處理器將按附加次序依次調用。如果某個處理器需要停止其后的處理器調用,可以設置?`$event`?參數的 [yii\base\Event::handled]] 屬性為真,如下:
~~~
$foo->on(Foo::EVENT_HELLO, function ($event) {
$event->handled = true;
});
~~~
默認新附加的事件處理器排在已存在處理器隊列的最后。因此,這個處理器將在事件被觸發時最后一個調用。在處理器隊列最前面插入新處理器將使該處理器最先調用,可以傳遞第四個參數?`$append`?為假并調用 yii\base\Component::on() 方法實現:
~~~
$foo->on(Foo::EVENT_HELLO, function ($event) {
// 這個處理器將被插入到處理器隊列的第一位...
}, $data, false);
~~~
## 觸發事件
事件通過調用 yii\base\Component::trigger() 方法觸發,此方法須傳遞**事件名**,還可以傳遞一個事件對象,用來傳遞參數到事件處理器。如:
~~~
namespace app\components;
use yii\base\Component;
use yii\base\Event;
class Foo extends Component
{
const EVENT_HELLO = 'hello';
public function bar()
{
$this->trigger(self::EVENT_HELLO);
}
}
~~~
以上代碼當調用?`bar()`?,它將觸發名為?`hello`?的事件。
> 提示:推薦使用類常量來表示事件名。上例中,常量?`EVENT_HELLO`?用來表示?`hello`?。這有兩個好處。第一,它可以防止拼寫錯誤并支持 IDE 的自動完成。第二,只要簡單檢查常量聲明就能了解一個類支持哪些事件。
有時想要在觸發事件時同時傳遞一些額外信息到事件處理器。例如,郵件程序要傳遞消息信息到?`messageSent`?事件的處理器以便處理器了解哪些消息被發送了。為此,可以提供一個事件對象作為 yii\base\Component::trigger() 方法的第二個參數。這個事件對象必須是 yii\base\Event 類或其子類的實例。如:
~~~
namespace app\components;
use yii\base\Component;
use yii\base\Event;
class MessageEvent extends Event
{
public $message;
}
class Mailer extends Component
{
const EVENT_MESSAGE_SENT = 'messageSent';
public function send($message)
{
// ...發送 $message 的邏輯...
$event = new MessageEvent;
$event->message = $message;
$this->trigger(self::EVENT_MESSAGE_SENT, $event);
}
}
~~~
當 yii\base\Component::trigger() 方法被調用時,它將調用所有附加到命名事件(trigger 方法第一個參數)的事件處理器。
## 移除事件處理器
從事件移除處理器,調用 yii\base\Component::off() 方法。如:
~~~
// 處理器是全局函數
$foo->off(Foo::EVENT_HELLO, 'function_name');
// 處理器是對象方法
$foo->off(Foo::EVENT_HELLO, [$object, 'methodName']);
// 處理器是靜態類方法
$foo->off(Foo::EVENT_HELLO, ['app\components\Bar', 'methodName']);
// 處理器是匿名函數
$foo->off(Foo::EVENT_HELLO, $anonymousFunction);
~~~
注意當匿名函數附加到事件后一般不要嘗試移除匿名函數,除非你在某處存儲了它。以上示例中,假設匿名函數存儲為變量`$anonymousFunction`?。
移除事件的全部處理器,簡單調用 yii\base\Component::off() 即可,不需要第二個參數:
~~~
$foo->off(Foo::EVENT_HELLO);
~~~
## 類級別的事件處理器
以上部分,我們敘述了在**實例級別**如何附加處理器到事件。有時想要一個類的所有實例而不是一個指定的實例都響應一個被觸發的事件,并不是一個個附加事件處理器到每個實例,而是通過調用靜態方法 yii\base\Event::on() 在**類級別**附加處理器。
例如,[活動記錄](http://www.yiichina.com/doc/guide/2.0/db-active-record)對象要在每次往數據庫新增一條新記錄時觸發一個 yii\db\BaseActiveRecord::EVENT_AFTER_INSERT 事件。要追蹤每個[活動記錄](http://www.yiichina.com/doc/guide/2.0/db-active-record)對象的新增記錄完成情況,應如下寫代碼:
~~~
use Yii;
use yii\base\Event;
use yii\db\ActiveRecord;
Event::on(ActiveRecord::className(), ActiveRecord::EVENT_AFTER_INSERT, function ($event) {
Yii::trace(get_class($event->sender) . ' is inserted');
});
~~~
每當 yii\db\BaseActiveRecord 或其子類的實例觸發 yii\db\BaseActiveRecord::EVENT_AFTER_INSERT 事件時,這個事件處理器都會執行。在這個處理器中,可以通過?`$event->sender`?獲取觸發事件的對象。
當對象觸發事件時,它首先調用實例級別的處理器,然后才會調用類級別處理器。
可調用靜態方法yii\base\Event::trigger()來觸發一個**類級別**事件。類級別事件不與特定對象相關聯。因此,它只會引起類級別事件處理器的調用。如:
~~~
use yii\base\Event;
Event::on(Foo::className(), Foo::EVENT_HELLO, function ($event) {
echo $event->sender; // 顯示 "app\models\Foo"
});
Event::trigger(Foo::className(), Foo::EVENT_HELLO);
~~~
注意這種情況下?`$event->sender`?指向觸發事件的類名而不是對象實例。
> 注意:因為類級別的處理器響應類和其子類的所有實例觸發的事件,必須謹慎使用,尤其是底層的基類,如 yii\base\Object。
移除類級別的事件處理器只需調用yii\base\Event::off(),如:
~~~
// 移除 $handler
Event::off(Foo::className(), Foo::EVENT_HELLO, $handler);
// 移除 Foo::EVENT_HELLO 事件的全部處理器
Event::off(Foo::className(), Foo::EVENT_HELLO);
~~~
## 全局事件
所謂**全局事件**實際上是一個基于以上敘述的事件機制的戲法。它需要一個全局可訪問的單例,如[應用](http://www.yiichina.com/doc/guide/2.0/structure-applications)實例。
事件觸發者不調用其自身的?`trigger()`?方法,而是調用單例的?`trigger()`?方法來觸發全局事件。類似地,事件處理器被附加到單例的事件。如:
~~~
use Yii;
use yii\base\Event;
use app\components\Foo;
Yii::$app->on('bar', function ($event) {
echo get_class($event->sender); // 顯示 "app\components\Foo"
});
Yii::$app->trigger('bar', new Event(['sender' => new Foo]));
~~~
全局事件的一個好處是當附加處理器到一個對象要觸發的事件時,不需要產生該對象。相反,處理器附加和事件觸發都通過單例(如應用實例)完成。
然而,因為全局事件的命名空間由各方共享,應合理命名全局事件,如引入一些命名空間(例:"frontend.mail.sent", "backend.mail.sent")。
- 介紹(Introduction)
- 關于 Yii(About Yii)
- 從 Yii 1.1 升級(Upgrading from Version 1.1)
- 入門(Getting Started)
- 安裝 Yii(Installing Yii)
- 運行應用(Running Applications)
- 第一次問候(Saying Hello)
- 使用 Forms(Working with Forms)
- 玩轉 Databases(Working with Databases)
- 用 Gii 生成代碼(Generating Code with Gii)
- 更上一層樓(Looking Ahead)
- 應用結構(Application Structure)
- 結構概述(Overview)
- 入口腳本(Entry Scripts)
- 應用(Applications)
- 應用組件(Application Components)
- 控制器(Controllers)
- 模型(Models)
- 視圖(Views)
- 模塊(Modules)
- 過濾器(Filters)
- 小部件(Widgets)
- 前端資源(Assets)
- 擴展(Extensions)
- 請求處理(Handling Requests)
- 運行概述(Overview)
- 引導(Bootstrapping)
- 路由引導與創建 URL(Routing and URL Creation)
- 請求(Requests)
- 響應(Responses)
- Sessions and Cookies
- 錯誤處理(Handling Errors)
- 日志(Logging)
- 關鍵概念(Key Concepts)
- 組件(Components)
- 屬性(Properties)
- 事件(Events)
- 行為(Behaviors)
- 配置(Configurations)
- 別名(Aliases)
- 類自動加載(Class Autoloading)
- 服務定位器(Service Locator)
- 依賴注入容器(Dependency Injection Container)
- 配合數據庫工作(Working with Databases)
- 數據庫訪問(Data Access Objects): 數據庫連接、基本查詢、事務和模式操作
- 查詢生成器(Query Builder): 使用簡單抽象層查詢數據庫
- 活動記錄(Active Record): 活動記錄對象關系映射(ORM),檢索和操作記錄、定義關聯關系
- 數據庫遷移(Migrations): 在團體開發中對你的數據庫使用版本控制
- Sphinx
- Redis
- MongoDB
- ElasticSearch
- 接收用戶數據(Getting Data from Users)
- 創建表單(Creating Forms)
- 輸入驗證(Validating Input)
- 文件上傳(Uploading Files)
- 收集列表輸入(Collecting Tabular Input)
- 多模型同時輸入(Getting Data for Multiple Models)
- 顯示數據(Displaying Data)
- 格式化輸出數據(Data Formatting)
- 分頁(Pagination)
- 排序(Sorting)
- 數據提供器(Data Providers)
- 數據小部件(Data Widgets)
- 操作客戶端腳本(Working with Client Scripts)
- 主題(Theming)
- 安全(Security)
- 認證(Authentication)
- 授權(Authorization)
- 處理密碼(Working with Passwords)
- 客戶端認證(Auth Clients)
- 安全領域的最佳實踐(Best Practices)
- 緩存(Caching)
- 概述(Overview)
- 數據緩存(Data Caching)
- 片段緩存(Fragment Caching)
- 分頁緩存(Page Caching)
- HTTP 緩存(HTTP Caching)
- RESTful Web 服務
- 快速入門(Quick Start)
- 資源(Resources)
- 控制器(Controllers)
- 路由(Routing)
- 格式化響應(Response Formatting)
- 授權驗證(Authentication)
- 速率限制(Rate Limiting)
- 版本化(Versioning)
- 錯誤處理(Error Handling)
- 開發工具(Development Tools)
- 調試工具欄和調試器(Debug Toolbar and Debugger)
- 使用 Gii 生成代碼(Generating Code using Gii)
- TBD 生成 API 文檔(Generating API Documentation)
- 測試(Testing)
- 概述(Overview)
- 搭建測試環境(Testing environment setup)
- 單元測試(Unit Tests)
- 功能測試(Functional Tests)
- 驗收測試(Acceptance Tests)
- 測試夾具(Fixtures)
- 高級專題(Special Topics)
- 高級應用模版(Advanced Project Template)
- 從頭構建自定義模版(Building Application from Scratch)
- 控制臺命令(Console Commands)
- 核心驗證器(Core Validators)
- 國際化(Internationalization)
- 收發郵件(Mailing)
- 性能優化(Performance Tuning)
- 共享主機環境(Shared Hosting Environment)
- 模板引擎(Template Engines)
- 集成第三方代碼(Working with Third-Party Code)
- 小部件(Widgets)
- Bootstrap 小部件(Bootstrap Widgets)
- jQuery UI 小部件(jQuery UI Widgets)
- 助手類(Helpers)
- 助手一覽(Overview)
- Array 助手(ArrayHelper)
- Html 助手(Html)
- Url 助手(Url)