Higher Order Messages 是一個像魔法一般的功能,先來看看官方提供的範例:
$invoices->each(function($invoice) {
$invoice->pay();
});
// 下面的呼叫結果相同
$invoices->each->pay();
// ------------
$employees->reject(function($employee) {
return $employee->retired;
})->each(function($employee){
$employee->sendPayment();
});
// 下面的呼叫結果相同
$employees->reject->retired->each->sendPayment();
原本要寫一堆 callback,現在只要使用一連串屬性的取法,就能得到一樣的結果。
以上例第一個例子為例:
$invoices->each->pay();
可以猜想得到,each 會是一個委任出去的角色,這從原始碼最上面註解的屬性可以找得到:
/**
* @property-read HigherOrderCollectionProxy $each
*/
通常 Laravel 或大部分開源的原始碼,會寫在最上面的屬性和方法,通常都是 magic method 實作出來的。本例是屬性,所以會是由 __get()
實作:
protected static $proxies = [
'average', 'avg', 'contains', 'each', 'every', 'filter', 'first',
'flatMap', 'groupBy', 'keyBy', 'map', 'max', 'min', 'partition',
'reject', 'sortBy', 'sortByDesc', 'sum', 'unique',
];
public function __get($key)
{
if (! in_array($key, static::$proxies)) {
throw new Exception("Property [{$key}] does not exist on this collection instance.");
}
return new HigherOrderCollectionProxy($this, $key);
}
這裡直接建構出 HigherOrderCollectionProxy 出來用,而帶入的 $key
以上例來說,會是 each
。這個元件的實作超級簡單,除了建構子單純是保存狀態之外,就只有實作兩個 magic method:
註:下面原始碼的
$this->method
,即為上面建構帶入的的$key
。
public function __get($key)
{
return $this->collection->{$this->method}(function ($value) use ($key) {
return is_array($value) ? $value[$key] : $value->{$key};
});
}
public function __call($method, $parameters)
{
return $this->collection->{$this->method}(function ($value) use ($method, $parameters) {
return $value->{$method}(...$parameters);
});
}
由原始碼可以看出,原本要寫的 callback,將會由委任的物件建立。同時也可以得知,要成為 Higher Order Messages 的一員,參數必須要有 callback。
而哪時會使用 __get()
,哪時會使用 __call()
,下面是一個比較容易理解的比較:
// __get()
$invoices->each->name;
// __call()
$invoices->each->pay();
接著來看 __get()
實際在做的事,裡面的 $key
指的是上例的 name
。因此原始碼與等價的程式碼比對如下:
return $this->collection->{$this->method}(function ($value) use ($key) {
return is_array($value) ? $value[$key] : $value->{$key};
});
return $this->collection->each(function ($value) {
return is_array($value) ? $value['name'] : $value->name;
});
同理,__call()
的 $key
是 pay
。原始碼與等價的程式碼比對如下:
return $this->collection->{$this->method}(function ($value) use ($method, $parameters) {
return $value->{$method}(...$parameters);
});
return $this->collection->each(function ($value) {
return $value->pay();
});
從上述等價原始碼可以了解,使用 Higher Order Messages 的時機會是,當方法是在 proxy 的列表裡,且 callback 裡面單純只回傳一個特定值、或呼叫一個特定方法時,就能考慮使用它。反之,需要兩行以上的程式碼時,就無法使用這個功能了。
Higher Order Messages 是個很有趣的實作,短短不到 100 行程式碼,就能讓使用它的開發者少寫非常多程式碼,而且可讀性也不差,真的是非常厲害。