Laravel Macroable 的使用需要对php闭包的相关知识有所了解,可以参看文档 php 闭包
Laravel Macroable 源码对应 \Illuminate\Support\Traits\Macroable 这个trait 文件,这里将其代码剥离出来单独运行、测试,以便研究。
其源码如下
// Macroable.php
<?php
trait Macroable {
/**
* The registered string macros.
* 变量是一个数组
* 键为方法名,值为闭包,对应此方法的具体实现
* @var array
*/
protected static $macros = [];
/**
* Register a custom macro.
* 注册方法,保存到 静态变量 $macros 中
* @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
* @param bool $replace 如果方法已经存在是否覆盖,true表示覆盖,false表示不覆盖,
* true 意味着生效的是第一次注册的闭包,false 则意味着生效的是最后一次注册的闭包
* @return void
*
* @throws \ReflectionException
*/
public static function mixin($mixin, $replace = true)
{
// 获取 $mixin 对象的所有公有和保护方法
$methods = (new ReflectionClass($mixin))->getMethods(
ReflectionMethod::IS_PUBLIC | ReflectionMethod::IS_PROTECTED
);
foreach ($methods as $method) {
// 依次激活该对象的每个方法,并将每个方法返回的匿名函数注册到 $macros 中
if ($replace || ! static::hasMacro($method->name)) {
$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
));
}
// 找到方法名对应的闭包
$macro = static::$macros[$method];
if ($macro instanceof Closure) {
// 绑定闭包到类
$macro = $macro->bindTo(null, static::class);
}
// 调用闭包
return $macro(...$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) {
// 绑定闭包到当前对象
$macro = $macro->bindTo($this, static::class);
}
// 调用闭包
return $macro(...$parameters);
}
}
其中 macro、hasMacro 方法比较简单,重点是 __callStatic、__call 和 mixin 方法,下面分别进行分析,
先看一个用法示例
require __DIR__.DIRECTORY_SEPARATOR.'Macroable.php';
class MacroTest
{
// 引入 Macroable trait
use Macroable;
}
// 注册一个名为 addedJoin 的方法,
// 作用是把多个字符串拼成一个字符串,字符串之间用 '-' 隔开
MacroTest::macro('addedJoin', function(...$string){
return implode('-', $string);
});
// 注册后,可以以静态方法的方式调用 addedJoin 方法
// 好像 MacroTest 类增加了一个静态方法
echo MacroTest::addedJoin('a','b','c'),"\n";
// MacroTest 对象也可以调用 addedJoin 方法
// 好像 MacroTest 类增加了一个普通的public 成员函数
$obj = new MacroTest();
echo $obj->addedJoin('a','b','c'),"\n";
必须先注册方法的闭包,否则会报异常,整个实现实际上归功于 Macroable 中定义的两个魔术方法:__callStatic 和 __call,PHP中如果调用的方法不存在,会触发调用相应的魔术方法,静态方法对应__callStatic方法,普通方法对应 __call方法。阅读代码会发现 Macroable 中两个魔术方法的实现是类似的,都是先根据方法名找到注册的闭包,然后进行绑定,最后调用绑定后的闭包。
虽然注册的方法同时支持静态和非静态两种调用方式,但具体应该怎么调用实际上取决于闭包的具体实现,如果注册的方法使用了 $this 那么显然只能通过类对象调用。
mixin 用于一次性把指定对象的所有公有和保护方法加到另一个类中,需要注意的是其中的注册语句
static::macro($method->name, $method->invoke($mixin))
$method->invoke($mixin)
这一句意味着注册是调用了对象相应方法的返回值,这意味着被添加的对象所有的公有、保护方法都需要返回一个闭包,这其实是一个很大的限制,因为这意味着公有的构造函数和返回对象本身的方法都不能有,也就是这个类不能产生对象,只能包含静态成员和返回闭包的接口。
class ObjNotOk
{
public function notReturnClosure(... $string)
{
// 这样的类对象不能用于mixin方法加到另一个类
return implode('-', $string);
}
}
下面是可用于mixin的类示例
<?php
require __DIR__ . DIRECTORY_SEPARATOR . 'Macroable.php';
class MacroTest
{
use Macroable;
}
class Str
{
public function join()
{
return function (...$string) {
return implode('-', $string);
};
}
public function split()
{
return function (string $string) {
return explode('-', $string);
};
}
}
MacroTest::mixin(new Str(100), true);
echo MacroTest::join('a', 'b', 'c'), "\n";
print_r(MacroTest::split('a-b-c'));
https://blog.csdn.net/larance001/article/details/121479499