Skip to content

Latest commit

 

History

History
174 lines (139 loc) · 4.25 KB

macro.md

File metadata and controls

174 lines (139 loc) · 4.25 KB

Macro

macro是一种为类/实例动态添加方法的技术。在Laravel的核心中,很多地方都使用了macro,譬如在模型的软删除中,当一个模型引入 了\Illuminate\Database\Eloquent\SoftDeletes这个特征后,它就会给模型查询构造器添加一些软删除相关的方法,像用于 从软删除中恢复模型的restore,囊括软删除模型的withTrashed,排除软删除模型的withoutTrashed,只考虑软删除模型的 onlyTrashed

它的实现很简单,主要就是使用了__call__callStatic这两个魔术方法。它本身是一个特征,所以当一个类实现了自己的__call 或者__callStatic时,当引入这个特征时,就要考虑覆盖的问题。

//src/Illuminate/Support/Traits/Macroable.php

/**
 * Register a custom macro.
 *
 * @param  string $name
 * @param  object|callable  $macro
 *
 * @return void
 */
public static function macro($name, $macro)
{
    static::$macros[$name] = $macro;
}

/**
 * Checks if macro is registered.
 *
 * @param  string  $name
 * @return bool
 */
public static function hasMacro($name)
{
    return isset(static::$macros[$name]);
}

macrohasMacro都很简单,就是用来注册macro以及判断macro是否存在

//src/Illuminate/Support/Traits/Macroable.php

/**
 * Dynamically handle calls to the class.
 *
 * @param  string  $method
 * @param  array   $parameters
 * @return mixed
 *
 * @throws \BadMethodCallException
 */
public static function __callStatic($method, $parameters)
{
    if (! static::hasMacro($method)) {
        throw new BadMethodCallException(sprintf(
            'Method %s::%s does not exist.', static::class, $method
        ));
    }

    if (static::$macros[$method] instanceof Closure) {
        return call_user_func_array(Closure::bind(static::$macros[$method], null, static::class), $parameters);
    }

    return call_user_func_array(static::$macros[$method], $parameters);
}

__callStatic用来处理静态的macro调用,这里要注意的是,当一个macro是\Closure实例时,把当前类的作用域绑定到这个实例中,否则会出现一种 情况就是在这个macro方法中,不能像普通的类静态方法一样,访问类的私有或者受保护属性和方法。

class Foo
{
    use Macroable;

    private static function bar()
    {

    }
}

Foo::macro('baz', function() {
    return static::bar();
});

//如果没有绑定作用域,那么就会出错
Foo::baz();
//src/Illuminate/Support/Traits/Macroable.php

/**
 * Dynamically handle calls to the class.
 *
 * @param  string  $method
 * @param  array   $parameters
 * @return mixed
 *
 * @throws \BadMethodCallException
 */
public function __call($method, $parameters)
{
    if (! static::hasMacro($method)) {
        throw new BadMethodCallException(sprintf(
            'Method %s::%s does not exist.', static::class, $method
        ));
    }

    $macro = static::$macros[$method];

    if ($macro instanceof Closure) {
        return call_user_func_array($macro->bindTo($this, static::class), $parameters);
    }

    return call_user_func_array($macro, $parameters);
}

__call__callStatic类似,但是它除了要绑定类的作用域,还要当前实例绑定到\Closurethis,这样,在closure中 使用的$this指向的就是当前实例。

除此以外,它还支持从其他类实例中混入它们的公共或者受保护方法

class Foo
{
    use Macroable;

}

class Bar
{
    public function baz()
    {
        return function() {

        };
    }
}

Foo::mixin(new Bar);

Foo::baz();

要注意的是混入方法的返回值要是callable类型的。

//src/Illuminate/Support/Traits/Macroable.php

/**
 * Mix another object into the class.
 *
 * @param  object  $mixin
 * @return void
 * @throws \ReflectionException
 */
public static function mixin($mixin)
{
    $methods = (new ReflectionClass($mixin))->getMethods(
        ReflectionMethod::IS_PUBLIC | ReflectionMethod::IS_PROTECTED
    );

    foreach ($methods as $method) {
        $method->setAccessible(true);

        static::macro($method->name, $method->invoke($mixin));
    }
}

它利用了反射机制,把要混入的实例中的公共和受保护方法都拿出来,执行一遍,然后注册到macro中。