我们以 Illuminate\Support\Traits\Macroable
为例分析。
通过 trait 可以很方便的在任何类中使用。
Laravel 提供的 Macroable 可以在不改变类结构的情况下为其扩展功能。也就是为一个类动态注入一些方法,并且和该类本身的方法拥有同样的作用域和调用方式。
Macroable 的核心是基于匿名函数的绑定功能,关于这一块请参考:https://blog.csdn.net/raoxiaoya/article/details/103894397
trait Macroable
{
/**
* The registered string macros.
*
* @var array
*/
protected static $macros = [];
/**
* Register a custom macro.
*
* @param string $name
* @param object|callable $macro
*
* @return void
*/
public static function macro($name, $macro)
{
static::$macros[$name] = $macro;
}
/**
* 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));
}
}
/**
* Checks if macro is registered.
*
* @param string $name
* @return bool
*/
public static function hasMacro($name)
{
return isset(static::$macros[$name]);
}
/**
* 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);
}
/**
* 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);
}
}
我们可以看到执行绑定是在 __callStatic 和 __call 里面,
如果一个类想要借助 Macroable 来提升能力,基本操作是:
1、use Macroable;
2、调用 macro 添加一个方法到 $macros
macro 可以添加匿名函数和对象,之所能通过调用匿名函数的方式调用对象,前提是该对象要实现 __invoke() 方法,也就是说,调用这个对象的入口是 __invoke() 方法。
例如:
class Foo
{
use Macroable;
}
final class Join
{
public function __invoke(...$string)
{
return implode('-', $string);
}
}
Foo::macro('join', new Join());
或者
Foo::macro('join', function(...$string){
return implode('-', $string);
});
3、或者调用 mixin 将一个对象包含的全部方法都注册到当前类中。
使用了反射类获取到public和protected的方法,并且将protected方法设置为可访问,最后调用了这些方法,将返回值注入到 $macros;我们知道注册进去的都是可以被当
作闭包调用的,因此我们反射的这个对象里面的方法的返回值应该是闭包,例如:
final class Str
{
public function join()
{
// 返回匿名函数
return function(...$string){
return implode('-', $string);
};
}
public function split()
{
// 返回匿名函数
return function(string $string){
return explode('-', $string);
};
}
}
执行注入
mixin(new Str())
为了更便捷的扩展一个类的功能,macro, mixin, hasMacro 三个方法都被设计为静态调用,可以通过类直接调用来扩展方法。
比如 Maatwebsite/Laravel-Excel 插件就是在服务容器provider中为集合类Collection扩展导出功能的
https://blog.csdn.net/raoxiaoya/article/details/103920781