Laravel集合探學系列——新增擴充套件macro策略(一)

劍歌丶君發表於2018-08-07

用過laravel的人,必定都用過其集合,各種對資料庫查詢出來的資料,進行隨意變換,來滿足複雜的業務需求。那每個集合的方法,又是如何實現的? 很好奇,裡面一定有各種奇思妙想,所以我想一探究竟。

這裡直接按照 laravel-china 的官網文件開搞~

具體的程式碼,就不一一copy了。可以參照著集合 collection.php 對著看。

1. Macroable

這是集合類裡 use 的 trait Macroable,主要用來自定義擴充套件集合類的方法的,下面仔細分析一下。

// 這裡得名稱空間和依賴的類就省略了
trait Macroable 
{
    /**
     * 定義陣列用來儲存註冊的擴充套件。
     *
     * @var array
     */
    protected static $macros = [];
    
    /**
     * 註冊自定製的方法macro。
     *
     * @param  string $name
     * @param  object|callable  $macro
     * @return void
     */
    public static function macro($name, $macro)
    {
        // 把方法存到定義的$macros裡。
        static::$macros[$name] = $macro;
    }
    
    /**
     * 將其他的類混合mix到這個Collection集合裡。
     *
     * @param  object  $mixin
     * @return void
     */
    public static function mixin($mixin)
    {
        // 通過反射 獲取所有傳過來的類 $mixin 裡的方法,並保留public 和 protected 方法
        $methods = (new ReflectionClass($mixin))->getMthods(
            ReflectionMethod::IS_PUBLIC | ReflectionMethod::IS_PROTECTED
        );
        // 將方法進行迴圈, 將所有都方法都設定為可以訪問執行
        foreach($methods as $method) {
            $method->setAccessible(true);
            
            // 利用invoke方法 來呼叫$mixin裡的 $method,並且執行靜態方法
            // macro 來儲存在$macros陣列裡。
            static::macro($method->name, $method->invoke($mixin));
        }
    }
    
    /**
     * 根據某個方法名判斷方法是否已註冊
     *
     * @param  string  $name
     * @return bool
     */
    public static function hasMacro($name)
    {
        return isset(static::$macros[$name]);
    }
    
    /**
     * 處理Collection類裡不存在的靜態方法呼叫
     * 也就是這裡自定義擴充的方法
     *
     * @param  string  $method
     * @param  array   $parameters
     * @return mixed
     *
     * @throws \BadMethodCallException
     */
    public static function __callStatic($method, $parameters)
    {
        // 如果要呼叫的方法並不存在,就丟擲錯誤
        if (! static::hasMacro($method)) {
            // 這個錯誤類 其實就是繼承了Exception類,沒別的啥
            throw new BadMethodCallException("Method {$method} does not exist.");
        }
        
        // 如果存在,並且是匿名函式
        if (static::$macros[$method] instanceof Closure) {
            // 首先用匿名函式類Closure::bind 這靜態方法來複制這個匿名函式,並且重新定義作用域和上下文$this
            // 因為是靜態方法,所以 $this 傳 null
            // 然後再用call_user_func_array 來呼叫執行匿名函式。
            return call_user_func_array(Closure::bind(static::$macros[$method], null, static::class), $parameters);
        }
        
        // 如果不屬於匿名函式,那麼就直接呼叫。
        return call_user_func_array(static::$macros[$method], $parameters);
    }
    
    /**
     * 處理Collection裡不存在的成員方法
     * 也就是擴充的方法
     *
     * @param  string  $method
     * @param  array   $parameters
     * @return mixed
     *
     * @throws \BadMethodCallException
     */
    public function __call($method, $parameters)
    {   
        // 如果要呼叫的方法並不存在,就丟擲錯誤
        if (! static::hasMacro($method)) {
            throw new BadMethodCallException("Method {$method} does not exist.");
        }
        
        // 若存在,將其賦值給$macro
        $macro = static::$macros[$method];
        
        // 如果是屬於匿名函式
        if ($macro instanceof Closure) {
            // 複製該閉包,併為其重新定義上下文$this,和作用域。
            return call_user_func_array($macro->bindTo($this, static::class), $parameters);
        }
        
        // 如果不是匿名函式,直接呼叫
        return call_user_func_array($macro, $parameters);
    }
}
複製程式碼

小結:
1.定義一個靜態變數來儲存方法。
2.mixin靜態方法,傳入類的例項,利用RelationClass類來獲取想要的型別方法,並且利用RelationMethod類裡的setAccessible方法使其可以執行。再用invoke方法進行執行,並存入到靜態變數裡。
3.利用魔術方法__call__callStatic來執行匿名函式

相關文章