Laravel框架事件實現

序言

若是你正在使用Laravel做爲你的開發框架,那麼確定會瞭解或者使用過它的事件API。Laravel的事件API是基於觀察者這種設計模式實現的,而且結合了實際狀況作了優化和改進。下面就依次介紹和Laravel事件實現相關的一些內容。php

觀察者模式

首先舉一個例子,假如你是一個司機,在馬路上開車時違反了交通規則,被交警攔下後罰了款。這是在現實中生活中應用到事件機制的一個例子。司機就是一個被觀察的對象,而交警就是觀察者,當交警觀察到司機違法時,就像被打開了一個開關同樣,會不自覺地對司機進行處罰。設計模式

下面是一張觀察者模式的類圖,左邊的Concrete Subject類就比如司機,而右邊的Concrete Observer就比如交警。Concrete Subject的Notify方法是將司機的違法行爲暴露給交警,而Concrete Observer的Update方法是交警的處罰行爲。框架

輸入圖片說明

Laravel事件與監聽器

輸入圖片說明

Laravel中的事件API包含了監聽(Listen)和觸發(Fire)兩個核心的方法,其中Listen方法就是將事件(Event)與監聽器(Listener)進行綁定,監聽和觸發方法都封裝在Dispatcher類中。Dispatcher類就是一個Concrete Subject,Event就比如司機的違法行爲,Fire方法就是上面說到的Notify方法,Listener就比如是交警,它是一個Concrete Observer,Listener類中須要實現handle方法用來作相似交警罰款的動做。異步

註冊事件與監聽器

輸入圖片說明

  • 判斷Listener是否爲字符串
public function makeListener($listener)
    {
        return is_string($listener) ? $this->createClassListener($listener) : $listener;
    }
  • 解析Listener類和handle方法,handle方法能夠替換成自定義的方法
protected function parseClassCallable($listener)
    {
        $segments = explode('@', $listener);

        return [$segments[0], count($segments) == 2 ? $segments[1] : 'handle'];
    }
  • 判斷Listener的handle方法是否須要隊列異步執行(考慮爲什麼須要異步執行?)
protected function handlerShouldBeQueued($class)
    {
        try {
            return (new ReflectionClass($class))->implementsInterface(
                'Illuminate\Contracts\Queue\ShouldQueue'
            );
        } catch (Exception $e) {
            return false;
        }
    }
  • 建立Listener的Callable
protected function createClassCallable($listener, $container)
    {
        list($class, $method) = $this->parseClassCallable($listener);

        if ($this->handlerShouldBeQueued($class)) {
            return $this->createQueuedHandlerCallable($class, $method);
        } else {
            return [$container->make($class), $method];
        }
    }
  • 判斷Event字符串是否包含通配符,把上面生成的Listener放入對應的類屬性,注意$priority
public function listen($events, $listener, $priority = 0)
    {
        foreach ((array) $events as $event) {
            if (Str::contains($event, '*')) {
                $this->setupWildcardListen($event, $listener);
            } else {
                $this->listeners[$event][$priority][] = $this->makeListener($listener);

                unset($this->sorted[$event]);
            }
        }
    }

    protected function setupWildcardListen($event, $listener)
    {
        $this->wildcards[$event][] = $this->makeListener($listener);
    }

觸發事件

輸入圖片說明

  • 判斷Event是否爲對象,解析生成payload和Event字符串
if (is_object($event)) {
            list($payload, $event) = [[$event], get_class($event)];
    }
  • 格式化payload,將event字符串寫入firing屬性
if (! is_array($payload)) {
            $payload = [$payload];
    }

    $this->firing[] = $event;
  • 經過隊列廣播事件
if (isset($payload[0]) && $payload[0] instanceof ShouldBroadcast) {
            $this->broadcastEvent($payload[0]);
    }
  • 根據Event字符串獲取Listeners
public function getListeners($eventName)
    {
        $wildcards = $this->getWildcardListeners($eventName);

        if (! isset($this->sorted[$eventName])) {
            $this->sortListeners($eventName);
        }

        return array_merge($this->sorted[$eventName], $wildcards);
    }
  • 對不包含通配符的事件Listener按註冊時的priority進行排序
protected function sortListeners($eventName)
    {
        $this->sorted[$eventName] = [];

        if (isset($this->listeners[$eventName])) {
            krsort($this->listeners[$eventName]);

            $this->sorted[$eventName] = call_user_func_array(
                'array_merge', $this->listeners[$eventName]
            );
        }
    }
  • 循環執行Listener Callable,payload做爲參數傳過去。$halt表示返回內容不爲空時中斷執行,當即返回響應的內容。執行結束後,將Event字符串從firing屬性中刪除。若是返回內容爲false,則跳出循環。
foreach ($this->getListeners($event) as $listener) {
            $response = call_user_func_array($listener, $payload);

            if (! is_null($response) && $halt) {
                array_pop($this->firing);

                return $response;
            }

            if ($response === false) {
                break;
            }

            $responses[] = $response;
    }

    array_pop($this->firing);

    return $halt ? null : $responses;

總結

Laravel的事件API支持一個事件綁定多個監聽器,也支持包含通配符的事件,可是包含通配符的事件不支持按優先級觸發監聽器。Laravel的事件觸發後,Listener能夠經過隊列異步執行,保證了程序能夠不中斷,且快速響應,就比如本文開頭的舉例中,若是被電子警察拍到違法行爲,能夠在指定時間內延遲繳納罰款。Laravel的事件也支持經過隊列廣播,Dispatcher能夠被Listener訂閱,在Listener中完成事件的註冊,便於更好地解耦和複用。優化

相關文章
相關標籤/搜索