Laravel集合探学系列——添加扩展macro策略(一)

510 阅读2分钟

用过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来执行匿名函数