在 Laravel
中,Collection
类本身实现了不少方法,比如 sum
、groupBy
等,若想给这个类加上一些自定义的方法,有如下方案:
方法一:通过修改 Collection
类的源码,在其中加入自定义方法的代码。但是这样就要修改框架源码,并且升级框架源码后,要把自定义方法重新在框架源码中加上,比较麻烦。
方法二:通过 Macroable
,在不修改框架源码的情况下,动态地把自定义方法加到 Collection
中。
本文主要介绍 Macroable
相关的知识。
Macroable 用法
Macroable
共有 5 个方法:macro
、minix
、hasMacro
、__callStatic
、__call
,其中后两个为魔术方法。接下来通过代码示例来了解这几个方法的用法
首先定义两个类:Car
、Plane
,后面的示例都是以这两个类为基础的。
Car
类中引用了 Macroable
这个 trait
,而 Plane
类中的 fly
方法,返回的是一个闭包。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
<?php
class Car
{
use Macroable;
private $name = 'php';
public function drive()
{
echo $this->name;
echo " drive car\n";
}
}
class Plane
{
public function fly()
{
return function(){
echo "plane can fly\n";
};
}
}
|
可在 Car
类中,通过 macro
方法给 Car
类动态添加方法,然后可将其作为方法、静态方法进行调用:
1
2
3
4
5
6
|
Car::macro('door', function(){
echo "open car door\n";
});
Car::door();
$car->door();
|
假如,我想使用某个类中的所有方法,可先用 minix
方法进行批量添加:
1
2
3
4
|
Car::mixin(new Plane());
$car->fly();
$car::fly();
|
注意 Plane
类中方法,返回的是闭包,否则用 minix
导入后会无法调用该方法。
Macroable 源码解析
本文分析的代码为 Lavavel 5.7
版本,Macroable
源码地址: Macroable 源码
Macroable.php
文件代码 100 行出头,其中还包括不少注释,因此是比较容易理解的,其结构如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
<?php
namespace Illuminate\Support\Traits;
trait Macroable
{
protected static $macros = [];
public static function macro($name, $macro){}
public static function mixin($mixin){}
public static function hasMacro($name){}
public static function __callStatic($method, $parameters){}
public function __call($method, $parameters){}
}
|
共 6 个方法,还有一个类静态变量,用来存储用户添加的自定义方法。
hasMacro
判断某个自定义方法是否存在
1
2
3
4
5
6
7
8
9
10
|
/**
* Checks if macro is registered.
*
* @param string $name
* @return bool
*/
public static function hasMacro($name)
{
return isset(static::$macros[$name]);
}
|
macro
添加一个自定义方法。其中 $macro
为对象或闭包。
如果传入的是对象,则该对象需实现 __invoke
魔术方法。
1
2
3
4
5
6
7
8
9
10
11
12
|
/**
* Register a custom macro.
*
* @param string $name
* @param object|callable $macro
*
* @return void
*/
public static function macro($name, $macro)
{
static::$macros[$name] = $macro;
}
|
minix
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
/**
* 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));
}
}
|
这里使用 php
的反射,首先获取 minix
对象中所有的 public
、protected
的方法。遍历各个方法,invoke
该方法,并把 invoke
的结果存入 macro
静态变量中。
上文提到过,被 mixin
的类中的方法,返回的必须是闭包,才能使用 mixin
方法被动态添加。
所以可能有人有这样的想法,把 $method->invoke($mixin)
改成 $method->getClosure($mixin)
,这样一来,被 mixin
的类中的方法就不用返回闭包,而是正常地写就行了。
实际上是不行的。因为通过 ReflectionMethod::getClosure()
获取的闭包,均不能进行重新绑定。
__callStatic
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
/**
* 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);
}
|
首先判断被调用的方法,是否已被添加到 macro
静态变量中。
如果被调用的方法是个闭包,则绑定 static
的类作用域,并调用该闭包。
否则,被调用的是对象(该对象需实现 __invoke
方法)。
__call
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
/**
* 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
参考资料
https://blog.csdn.net/pharaoh_shi/article/details/80984437
https://www.php.net/manual/zh/class.reflectionclass.php
https://learnku.com/laravel/t/2915/how-to-use-the-macro-method-to-extend-the-function-of-the-base-class-of-laravel
https://stackoverflow.com/questions/47165930/php-binding-method-to-another-class