Laravel集合探学系列——高阶消息传递实现(二)

1,687 阅读2分钟

何谓 高阶消息传递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 就是很好的例子,上面有解析。