LaravelのHigherOrderCollectionProxyの仕組み

[HigherOrderCollectionProxy](https://github.com/laravel/framework/blob/5.8/src/Illuminate/Support/HigherOrderCollectionProxy.php)とは

```php:PHP
$collection = collect([collect([1, 2, 3, 4, 5]), collect([6, 7, 8, 9, 10])])->map(function ($collection) {
    return $collection->average();
});

$collection = collect([collect([1, 2, 3, 4, 5]), collect([6, 7, 8, 9, 10])])->map->average();
```

上を下のようにメソッドチェーンで繋げられるようにしたものです。


`collect([collect([1, 2, 3, 4, 5]), collect([6, 7, 8, 9, 10])])->map`で`__get()`メソッドを呼び`HigherOrderCollectionProxy`オブジェクトを返します。

```php:/laravel/framework/src/Illuminate/Support/HigherOrderCollectionProxy.php
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);
}
```

phpではクラスに存在しないプロパティ（ここではmap)を読み込むとマジックメソッド`__get()`を呼び出します。
`$key`はプロはティ名の文字列です。
そして`static::$proxies`の中にmapが入っているかをチェックしてから、`HigherOrderCollectionProxy`を返します。
第一引数には`collect([collect([1, 2, 3, 4, 5]), collect([6, 7, 8, 9, 10])])`、
第二引数には`'map'`が入ります。

```php:/laravel/framework/src/Illuminate/Support/Collection.php
/**
 * The methods that can be proxied.
 *
 * @var array
 */
protected static $proxies = [
    'average', 'avg', 'contains', 'each', 'every', 'filter', 'first',
    'flatMap', 'groupBy', 'keyBy', 'map', 'max', 'min', 'partition',
    'reject', 'some', 'sortBy', 'sortByDesc', 'sum', 'unique',
];
```

```php:/laravel/framework/src/Illuminate/Support/HigherOrderCollectionProxy.php
public function __construct(Collection $collection, $method)
{
    $this->method = $method;
    $this->collection = $collection;
}
```

`$this->method`に`'map'`、`$this->collection`に```Collection```オブジェクトが入ります。

そして`->average()`を呼び出します。
`HigherOrderCollectionProxy`には`average()`メソッドはないので、マジックメソッド`__call()`が呼ばれます。


```php:/laravel/framework/src/Illuminate/Support/HigherOrderCollectionProxy.php
/**
 * Proxy a method call onto the collection items.
 *
 * @param  string  $method
 * @param  array  $parameters
 * @return mixed
 */
public function __call($method, $parameters)
{
    return $this->collection->{$this->method}(function ($value) use ($method, $parameters) {
        return $value->{$method}(...$parameters);
    });
}
```

`$method`はメソッド名の文字列（ここでは`'average'`)、`$parameters`は引数を格納した入れ配列（`average()`は引数がないので空の配列)になります。

つまり`__call()`の中身は

```php:PHP
return $this->collection->map(function ($value) use ('average', []) {
    return $value->average(...[]);
});
```

ということになります。

```PHP:/laravel/framework/src/Illuminate/Support/Collection.php
/**
 * Run a map over each of the items.
 *
 * @param  callable  $callback
 * @return static
 */
public function map(callable $callback)
{
    $keys = array_keys($this->items);

    $items = array_map($callback, $this->items, $keys);

    return new static(array_combine($keys, $items));
}
```

`$callback`は`function ($value) use ('average', []) { return $value->average(...[]); }`、
`$this->items`は`[collect([1, 2, 3, 4, 5]), collect([6, 7, 8, 9, 10])]`、
`$keys`は`[0, 1]`です。

>array_map() は、array1 の各要素に callback 関数を適用した後、 その全ての要素を含む配列を返します。

[array_map()](https://www.php.net/manual/ja/function.array-map.php)

つまり`__call()`の`$value`にはそれぞれ、collect([1, 2, 3, 4, 5]とcollect([6, 7, 8, 9, 10])が入ります。

そして`average()`で平均が計算され

```php:PHP
var_dump($collection->all());
/*
array(2) {
  [0]=>
  int(3)
  [1]=>
  int(8)
}
*/
```

となります。