Laravel集合探學系列——高階訊息傳遞實現(二)

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

何謂 高階訊息傳遞Higher Order Messages(HOM) 其實就是個叫法,我也不太清楚。比如 each 方法,就屬於集合的高階訊息的方法內,要求應該就是引數可傳一個閉包,可以這麼理解。

1. 示例

先簡單說說它怎麼個用法,官方文件是有的,拿過來介紹一下。

// 在某控制器的方法裡
$users = User::where('votes', '>', 500)->get();

$users->each->markAsVip();

// user模型裡
public function markAsVip()
{
    // 這裡假設就是這麼更改,有is_vip這個欄位
    $this->update('is_vip', 1);
}
複製程式碼

以上,就屬於高階用法的例項。將 user 表裡的 votes 數大於500的標記為vip會員。 如果是正常寫法,那鐵定就是,將 $users 進行 foreach 遍歷 然後進行逐條更新。相比集合高階用法。就不必多說了吧。

2. Collection 裡 涉及高階訊息原始碼解析

/**
     * 可以被作為高階訊息傳遞的方法
     *
     * @var array
     */
    protected static $proxies = [
        'average', 'avg', 'contains', 'each', 'every', 'filter', 'first', 'flatMap',
        'keyBy', 'map', 'partition', 'reject', 'sortBy', 'sortByDesc', 'sum', 'unique',
    ];
    
    /**
     * 新增自定義高階方法
     *
     * @param  string  $method
     * @return void
     */
    public static function proxy($method)
    {
        static::$proxies[] = $method;
    }
    
    public function __get($key)
    {
        // 就是如果傳的 $key 在高階方法陣列裡都不存在,那麼報錯
        if (! in_array($key, $static::$proxies)) {
            throw new Exception("Property [{$key}] does not exist on this collection instance.")
        }
        // 否則, 將Collection例項和這個方法名以引數
        // 傳給 HigherOrderCollectionProxy 例項化
        return new HigherOrderCollectionProxy($this, $key);
    }
複製程式碼

上面,就是定義了靜態變數,存放高階方法名,還有可自定義的方法,和利用魔術方法__get() 來跳轉到 HigherOrderCollectionProxy 類裡得以實現。

3.HigherOrderCollectionProxy 類解析

  • 成員變數和構造方法
class HigherOrderCollectionProxy {
    /**
     * 儲存 collection類例項
     *
     * @var \Illuminate\Support\Collection
     */
    protected $collection;
    
    /**
     * 儲存 高階方法名
     *
     * @var string
     */
    protected $method;
    
    /**
     * 構造接收傳過來的 collection類例項,  高階方法名
     *
     * @param  \Illuminate\Support\Collection  $collection
     * @param  string  $method
     * @return void
     */
    public function __construct(Collection $collection, $method) 
    {
        // 簡單初始化賦值儲存
        $this->method = $method;
        $this->collection = $collection;
    }
    
複製程式碼
  • __get方法解析
    /**
     * 用來 針對屬性 高階傳遞呼叫
     * 
     * @param  string  $key
     * @return mixed
     */
    public function __get($key)
    {   
        return $this->collection->{$this->method}(function ($value) use ($key) {
            return is_array($value) ? $value[$key] : $value->{$key};
        });
    }
    // 說明:為了某些集合方法的引數-閉包裡 傳屬性的東東
    // 舉例: 將下列陣列按年齡正序排序
    $arr = [
        [
            'name' => '小明',
            'age' => 23,
            'sex' => '男'
        ],
        [
            'name' => '小鑫',
            'age' => 21,
            'sex' => '女'
        ]
        [
            'name' => '小hu',
            'age' => 22,
            'sex' => '男'
        ]
    ]; 
    collect($arr)->sortBy->age;
    // 將__get 解析一下:
    $age = 'age'
    $datas = collect($arr)->sortBy(function($value) use ($age) {
        return $value[$age];
    })
    $datas->values()->all();
    /*
        [
            [
                'name' => '小鑫',
                'age' => 21,
                'sex' => '女'
            ],
            [
                'name' => '小hu',
                'age' => 22,
                'sex' => '男'
            ],
            [
                'name' => '小明',
                'age' => 23,
                'sex' => '男'
            ],
        ]
    */
    
    // 這樣就清晰明瞭了。
複製程式碼
  • __call()方法解析
    /**
     * 針對方法的 高階訊息傳遞
     *
     * @param  string  $method
     * @param  array  $parameters
     * @return mixed
     */
    public function __call($method, $parameters)
    {
        return $this->collection->{$this->method}($value) use ($method, $parameters) {
            return $value->{$method}(...$parameters);
        }
    }
    // 說明: 針對高階集合呼叫方法,繼續舉例子
    
    // 舉例:再把第一節的例子拿來
    
    // 在某控制器的方法裡
    $users = User::where('votes', '>', 500)->get();
    $users->each->markAsVip();
    // user模型裡
    public function markAsVip()
    {
        // 這裡假設就是這麼更改,有is_vip這個欄位
        $this->update('is_vip', 1);
    }
    
    // 按__call()方法解析後
    $this->collection 就是 $users
    $this->method 就是 'each'
    $method 就是 'markAsVip'
    $parameters 就是 空的
    // 所以呢
    $method = 'markAsVip'
    $users->each(function($value) use ($method) {
        return $value->$method();
    })
    // 這樣不就是用each將$users遍歷 然後呼叫User模型裡的markAsVip()方法麼
    // 其實就是這樣 啊哈哈哈哈。
複製程式碼

4.總結

  • __get方法:當用例項呼叫獲取屬性的時候,那麼會自動呼叫該類裡的__get方法。 而高階訊息傳遞類HigherOrderCollectionProxy就利用這個作為跳板,進一步處理。
  • __call方法:當用例項呼叫某個成員方法的時候,不存在,就會自動呼叫該類的__call方法。 如集合的 sumeach 就是很好的例子,上面有解析。

相關文章