<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                企業??AI智能體構建引擎,智能編排和調試,一鍵部署,支持知識庫和私有化部署方案 廣告
                [TOC] # 說明 前面分析完一個請求的簡單生命周期,其中涵蓋了依賴注入、中間件的分析,這些不在單獨分析。接下來將分析框架的事件機制。 # 事件配置文件的載入 ## 準備工作 項目根目錄命令行運行:`php think make:listen ShowAppInit`創建一個監聽器,這將在`app\listener`目錄下生成一個`ShowAppInit.php`文件(如果沒有`listener`目錄則創建之)。接著簡單修改`ShowAppInit.php`文件的代碼如下: ``` <?php namespace app\listener; class ShowAppInit { public function handle($event) { echo "App 初始化啦" .PHP_EOL; } } ``` 監聽器創建完成后,將其添加到`app`目錄下的`event.php`文件: ``` return [ 'bind' => [ ], 'listen' => [ 'AppInit' => [ 'app\listener\ShowAppInit' ], //添加在這里 'HttpRun' => [], 'HttpEnd' => [], 'LogLevel' => [], 'LogWrite' => [], ], 'subscribe' => [ ], ]; ``` 這樣就綁定了一個監聽器(觀察者)到`AppInit`事件,一旦該事件被觸發,監聽器將開始工作——執行其`handle`方法下的代碼。 ## 配置載入 上面綁定監聽器后,系統是在哪里載入了這些配置呢? 順著一個請求的生命周期:Http::run()→Http::runWithRequest()→Http::initialize()->App::initialize()→App::load(),發現在`load`方法有這樣幾行: ``` if (is_file($appPath . 'event.php')) { $this->loadEvent(include $appPath . 'event.php'); } ``` 就是在這個位置,執行`loadEvent`方法加載事件的配置——該方法代碼如下: ``` public function loadEvent(array $event): void { if (isset($event['bind'])) { // 將事件標識到事件(操作,比如一個控制器操作)的映射合并到「Event」類「$bing」成員變量中 // 比如 'UserLogin' => 'app\event\UserLogin', $this->event->bind($event['bind']); } if (isset($event['listen'])) { // 合并所有觀察者(監聽者)到Event類的listener數組 // 其形式為實際事件(被觀察者)到觀察者的映射 $this->event->listenEvents($event['listen']); } if (isset($event['subscribe'])) { // 訂閱,實際上是一個批量的監聽 // 就像一個人他同時訂閱天氣預報、股市行情、小花上QQ了…… // 一個訂閱器,里面可以實現多個事件的監聽 // 比如,我在一個訂閱器中,同時監聽用戶登錄,用戶退出等操作 $this->event->subscribe($event['subscribe']); } } ``` 最終得到的`Event`類對象大概如下: ![](https://img.kancloud.cn/48/57/4857660699f0b2fabccc98e4d2c277ce_397x256.PNG) # 監聽器執行 事件監聽器綁定到事件之后,框架在初始化過程中,將這些配置加載到`Event`類的對象(當然也可以在程序中手動綁定監聽器),接下來就可以決定在何時觸發事件。`AppInit`事件是在`App::initialize()`方法中觸發的,其代碼如下: ``` $this->event->trigger('AppInit'); ``` 接著,我們看看`trigger`方法是如何觸發事件的(如何調用監聽器的`handle`方法)——其代碼如下: ``` public function trigger($event, $params = null, bool $once = false) { // A 如果設置了關閉事件,則直接返回,不再執行任何監聽器 if (!$this->withEvent) { return; } // B // 如果是一個對象,解析出對象的類 if (is_object($event)) { //將對象實例作為傳入參數 $params = $event; $event = get_class($event); } //根據事件標識解析出實際的事件 if (isset($this->bind[$event])) { $event = $this->bind[$event]; } $result = []; // 解析出事件的監聽者(可多個) $listeners = $this->listener[$event] ?? []; foreach ($listeners as $key => $listener) { // C // 執行監聽器的操作 $result[$key] = $this->dispatch($listener, $params); // 如果返回false,或者沒有返回值且 $once 為 true,直接中斷,不再執行后面的監聽器 if (false === $result[$key] || (!is_null($result[$key]) && $once)) { break; } } // 是否返回多個監聽器的結果 // $once 為 false 則返回最后一個監聽器的結果 return $once ? end($result) : $result; } ``` ## A 決定是否繼續執行監聽器 `trigger`方法首先通過`$this->withEvent`判斷監聽器是否要執行,如果為否,則直接終止該方法。 `withEvent`的值可以通過如下方法設定: * 配置文件中,通過設置`app.with_event`的值。該值在`Http::runWithRequest()`方法中讀取進來: ``` $this->app->event->withEvent($this->app->config->get('app.with_event', true)); ``` 由此,我們可以在配置文件中全局開啟或者關閉事件機制。 * 通過`Event::withEvent`方法設置。由此也可以得知,我們在執行完一個監聽器之后,可以通過`Event::withEvent`方法設置后面的監聽器是否還要執行。 ## B 事件標識解析 這里傳給`trigger`方法的是一個字符串(事件標識)`AppInit`,通過`$event = $this->bind[$event]` 得到`$event`的值為`think\event\AppInit`,再將該值作為鍵,`$listeners = $this->listener[$event]`,從`listener` 數組中獲取實際的監聽器,這里將得到`$listeners`為`[app\listener\ShowAppInit]`。 ## C 執行監聽器 這里主要看`dispatch`方法: ``` protected function dispatch($event, $params = null) { // 如果不是字符串,比如,一個閉包 if (!is_string($event)) { $call = $event; //一個類的靜態方法 } elseif (strpos($event, '::')) { $call = $event; } else { $obj = $this->app->make($event); $call = [$obj, 'handle']; } return $this->app->invoke($call, [$params]); } ``` 不管是閉包、靜態類方法,還是監聽器的`handle`方法,都是通過`invoke`方法來執行,`invoke`方法實現如下: ``` public function invoke($callable, array $vars = [], bool $accessible = false) { // 如果$callable是閉包 if ($callable instanceof Closure) { return $this->invokeFunction($callable, $vars); } // $callable不是閉包的情況 return $this->invokeMethod($callable, $vars, $accessible); } ``` 最終通過PHP反射類來執行對應的方法。 ## D 是否中斷和返回結果 從代碼實現可以看出,如果一個監聽器方法最終返回false,或者沒有返回值且 $once 為 true,則不再執行后面的監聽器。`trigger`方法是返回多個監4聽器的執行結果還是最后一個,由最后一個參數`$once`決定,`$once`為`true`,只返回最后一個監聽器執行結果,反之,返回所有結果組成的數組。 # 監聽器參數傳遞以及事件類 注意到監聽器的`handle`方法還可以接收一個參數,從上面的分析可知,`trigger`方法的第二個參數最終將傳給`handle`方法。 那么,在什么情況下需要用到這個參數呢?舉個例子,假如要監聽一個用戶登錄,我們可以新建一個監聽器,綁定事件標識,在`handle`方法中實現業務邏輯——例如,輸出:「有用戶登錄啦」,然后在登錄代碼的后面`trigger`這個事件標識。但如果我們又要知道是誰登錄的話,這時我們可以把用戶名作為`trigger`的第二個參數傳入,在監聽器的`handle`方法可以這樣使用:`echo $event . 用戶登錄啦`。 當然,這里的`$event`也可以是一個事件類對象。 # 訂閱 這里的訂閱,本質上一種「復合」的監聽器,比如,小明同時要監聽小花跑、跳、吃飯、睡覺,這時,就可以把小明要針對這些動作做出的反應都放在一個類里面,方便管理。 ## 舉個例子以及分析 ### 準備工作 * 在`app`目錄下的`event.php`文件中的`listen`鍵添加兩個事件標識,如下所示: ``` 'listen' => [ 'UserLogin' => [], 'UserLogout' => [], ], ``` 如果沒有這兩個事件標識,訂閱類的方法將無法被添加到`Event`類的`$listener` 數組,導致最后無法執行到訂閱類的方法的。 * 創建一個訂閱類 項目根目錄下,命令行運行:`php think make:subscribe User`,會在`app/subscribe`目錄下創建一個訂閱類,在該類中添加以下方法(如代碼所示): ``` class User { public function onUserLogin(){ echo '我知道用戶登錄了,因為我訂閱了<br>'; } public function onUserLogout(){ echo '我知道用戶退出了,因為我訂閱了<br>'; } } ``` * 創建控制器 在`app/controller`目錄下創建一個`User`控制器,添加代碼如下: ``` class User { public function __construct(){ //添加一個訂閱類 Event::subscribe(\app\subscribe\User::class); } public function login(){ echo "用戶登錄了<br> "; Event::trigger('UserLogin'); } public function logout(){ echo "用戶退出了<br> "; Event::trigger('UserLogout'); } } ``` ### 分析 假如訪問`User`控制器的`login`操作,調試運行代碼到`Event::subscrible()`方法,該方法代碼如下: ``` public function subscribe($subscriber) { if (!$this->withEvent) { return $this; } // 強制轉換為數組 $subscribers = (array) $subscriber; foreach ($subscribers as $subscriber) { if (is_string($subscriber)) { //實例化事件訂閱類 $subscriber = $this->app->make($subscriber); } // 如果該事件訂閱類存在'subscribe'方法,執行該方法 if (method_exists($subscriber, 'subscribe')) { // 手動訂閱 $subscriber->subscribe($this); } else { // 智能訂閱 $this->observe($subscriber); } } return $this; } ``` 這里關鍵看`observe`方法: ``` public function observe($observer, string $prefix = '') { if (!$this->withEvent) { return $this; } if (is_string($observer)) { $observer = $this->app->make($observer); } $reflect = new ReflectionClass($observer); $methods = $reflect->getMethods(ReflectionMethod::IS_PUBLIC); // 支持訂閱類中的方法指定前綴 if (empty($prefix) && $reflect->hasProperty('eventPrefix')) { $reflectProperty = $reflect->getProperty('eventPrefix'); $reflectProperty->setAccessible(true); $prefix = $reflectProperty->getValue($observer); } foreach ($methods as $method) { $name = $method->getName(); if (0 === strpos($name, 'on')) { // 自動將訂閱類的方法添加到監聽器 $this->listen($prefix . substr($name, 2), [$observer, $name]); } } return $this; } ``` 運行過程見上面的注釋。最后的結果大概是這樣的: ![](https://img.kancloud.cn/eb/48/eb48b202bbc4eb3b83f3106dcf6b8b4d_458x400.PNG) 在`UserLogin`和`UserLogout`事件標識下,分別添加了對應的監聽器,分別是事件訂閱類`app\subscribe\User`的`onUserLogin`和`onUserLogout`。 監聽器添加完成后,接著是等待事件觸發。`login`操作中,執行了`Event::trigger('UserLogin');`,這將執行`app\subscribe\User`的`onUserLogin`,其執行過程跟前面分析的執行事件監聽器是一樣的,結果輸出如下: ``` 用戶登錄了 我知道用戶登錄了,因為我訂閱了 ``` 同理,訪問`User`控制器的`logout`操作,得到: ``` 用戶退出了 我知道用戶退出了,因為我訂閱了 ``` 事件訂閱分析完畢。總結一下,事件訂閱就是一種「復合」的監聽器,可以同時監聽多個事件。從其實現過程來看,本質和事件監聽器是一樣的,個人認為,使用事件訂閱的好處是僅僅集中管理代碼,把對某個對象(被觀察者)的多個動作的監聽,都寫在一個事件訂閱類里面,因而就不用另外寫相應多個動作的監聽器類。 # 關于觀察者模式 事件的實現機制,實際上是使用觀察者模式實現的。觀察者模式的好處是實現類的松耦合,被觀察者不需要知道觀察者到底做了什么,只需要觸發事件就夠了;另外,觀察者的數量可以靈活地增加、減少,而不用修改被觀察者。深入理解觀察者模式,可以參考這篇:[學好事件,先學學觀察者模式](https://segmentfault.com/a/1190000009459014)
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看