华子的笔记 Help

Laravel中的Macroable trait原理

基本原理

利用PHP中的匿名函数可以动态绑定作用域来扩展类或者实例的功能。

定义类:

class A { }

定义匿名函数:

$sum = function (...$i) { $sum = 0; foreach ($i as $n) { $sum += $n; } return $sum; };

使用匿名函数基类Closure的三个方法bindTobindcall 为类的实例添加 sum 功能

$ASum = $sum->bindTo(new A(), A::class); $ASum(1, 2, 3); // 6 // or // $sum->call(new A(), 1, 2, 3); // or 静态绑定 // $staticASum = \Closure::bind($sum, null, A::class); // $staticASum(1, 2, 3);

Laravel中的 Macroable trait的实现

Illuminate\Support\Traits\Macroable是对上述基本原理的封装, $macros属性数组用来保存用户添加的扩展功能

trait Macroable { // 保存用户添加的扩展功能 protected static $macros = []; // 新注册一个扩展功能 public static function macro($name, $macro) { static::$macros[$name] = $macro; } }

当需要为特定类添加扩展功能时,添加Macroable trait

class A { use Macroable; }

添加sum功能到 A

A::macro("sum", function (...$i) { $sum = 0; foreach ($i as $n) { $sum += $n; } return $sum; });

调用方法如下

A::sum(1,2,3); // or (new A())->sum(1, 2, 3);

调用上述A不存在的方法需要使用魔术方法__callStatic__call

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); } 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); }

上面两个方法同时支持扩展匿名函数和类实例对象,只需要将匿名函数换成支持函数调用的类即可

class Sum { public function __invoke(int ...$i): int { $sum = 0; foreach ($i as $n) { $sum += $n; } return $sum; } }

这样使用

A::macro("sum", new Sum()); A::sum(1,2,3); // or (new A())->sum(1, 2, 3);

mixin 注入对象的多个方法

mixin静态方法通过反射机制注入对象的多个非私有方法

public static function mixin($mixin, $replace = true) { $methods = (new ReflectionClass($mixin))->getMethods( ReflectionMethod::IS_PUBLIC | ReflectionMethod::IS_PROTECTED ); foreach ($methods as $method) { if ($replace || ! static::hasMacro($method->name)) { $method->setAccessible(true); static::macro($method->name, $method->invoke($mixin)); } } }

举例

class Math { public function sum(): Closure { return function (int ...$i) { $sum = 0; foreach ($i as $n) { $sum += $n; } return $sum; }; } public function minus(): Closure { return function (int $a, $b) { return $a - $b; }; } } A::mixin(new Math()); echo A::sum(1, 2, 3); //6 echo A::minus(20, 1); //19

最后给类加上标签方便IDE代码提示

use Illuminate\Support\Traits\Macroable; /** * @method static int sum(int ...$i) * @method int sum(int ...$i) * @method static int minus(int $a, $b) */ class A { use Macroable; }
19 November 2023