##“macro” 缘起
“macro” 也就是宏的概念,在计算机科学和编程世界中有很长的历史了,也就意味着在不同的情境下有不同的含义。
可能大部分人第一次接触宏是在 basic/vb-script 中,那时候宏就是一个没有参数的函数。 C 语言也有宏,在程序编译前会进行宏名的字符替换。
甚至 MicroSoft 的 Office 也有宏,为了避免一再地重复相同的动作,就会把常用的动作写成宏,自动化的完成某项任务。
虽然每个宏的定义不同,但是他们具有一些共有的特性
都可以执行某个特定的任务,但是对整个编程环境只有有限的访问权。
接下来我们来看看 laravel 中的 marcro。
##microable 在 laravel 中的含义
首先我们举个栗子,为了方便测试,添加如下代码到 app/routes.php
中
1 | #File: app/routes.php |
如果我们在浏览器中访问 test
这个路由,显然会报错
call to undefined method Hello:sayHi();
原因是我们没有在 Hello 这个类中定义 sayHi 的方法。
这次我们把 hello 类简单的改造一下
1 | #File: app/routes.php |
注意这里的 macroableTrait 用了完整的命名空间,使用 trait 也会触发 php 的 autoloader.
重新测试,依然报错
Call to undefined method Hello::sayHi()
现在试试加入如下代码
1 | #File: app/routes.php |
注意到这里的静态方法 macro 是从
Illuminate\Support\Traits\MacroableTrait
中继承而来
刷新页面,我们可以看到如下输出
Hello
Hello
以上就是 MacroableTrait 的一个简单使用,当我们调用 macro 方法时,我们实际上给 Hello 类以及所有 Hello 类实例化的对象添加了一个 sayHi 的方法。
在 laravel 中的比较常见的使用方式是在一些带有 bootstrap 性质的引导脚本中,通过预先注册一些宏,使得开发者可以在其他地方调用已注册的宏。
比如 Form, response
MacroableTrait 原理
MacroableTrait 的代码其实很简单,我们可以通过如下路径找到它vendor/laravel/framework/src/Illuminate/Support/Traits/MacroableTrait.php
我们先来看看之前使用过的 macro
方法是如何定义
1 | #File: vendor/laravel/framework/src/Illuminate/Support/Traits/MacroableTrait.php |
macro
方法接受两个参数,一个是 string 类型的 $name, 用于标识具体某个宏的名字,第二个参数是
一个 callable 的参数,意味着可以是匿名函数,或者静态方法,又或者实例对应的某个方法,总之一切
可以 callable 的东西,以 key/value 的形式存储在一个静态变量 $macros 中。
往下看我们可以看到熟悉的 _call
以及 __callStaic
方法
当触发 __call
方法时
1 | #File: vendor/laravel/framework/src/Illuminate/Support/Traits/MacroableTrait.php |
会把对应的 $method 以及 $parameters 参数继续传递给 __callStatic
方法, 由它去统一接管.
而 __callStatic
方法
1 | #File: vendor/laravel/framework/src/Illuminate/Support/Traits/MacroableTrait.php |
会取出 $method 对应在 $macros 中注册的 callable 参数,通过 call_user_func_array
来真正的触发。
拿开头的例子来实例说明,首先当我们的 Hello 类调用 macro 方法时
1 | #File: app/routes.php |
我们告诉 Hello 类在 static::$macros 中注册一个名为 sayHi , 值为匿名函数的一个宏。
相当于 static::$macros['sayHi'] = function(){echo "Hello", "\n<br>\n";}
接着当我们在调用 sayHi 方法 (通过静态和实例方法两种方式) 时,
1 | #File: app/routes.php |
由于 sayHi
方法没有在 Hello 类中定义,php 会尝试去调用 __call
(-> 调用时) 和 __callStatic
(:: 静态调用时),
而__call
将方法以及参数又显式的传递给了 __callStatic
, 那么意味着
__callStatic
才是两种方式调用时真正执行的方法。
如果我们把参数的传入过程再具体化,如下图所示
1 | #File: vendor/laravel/framework/src/Illuminate/Support/Traits/MacroableTrait.php |
取出 sayHi 对应的匿名函数,调用 call_user_func_array 执行。
1 | #File: vendor/laravel/framework/src/Illuminate/Support/Traits/MacroableTrait.php |
以上就是全部的执行过程。
When to Use
尽管 MacroableTrait 很强大, 但并不意味着所有的类都适合用它, macro 无法知道类或者对象中的其他属性和方法。
由于我们注册进 macros 中的是匿名函数 (或者 php 的 callback), 也就是说这些 function/callback 是无法访问 $this , self 或者 static 的。
因此,通过 MacroableTrait
注册的宏更像是一个个无状态的 helper 方法。如果你需要在对象或者类中获取相关的一些状态,那么不合适用MacroableTrait
。
## 总结
通过利用 php 本身的魔术方法和静态的方式,
MacroableTrait
提供了一种在运行时动态为对象创建方法的能力,这种方式也是模拟了在其他动态语言中的特色 (比如 python/ruby)
但是要注意 MacroableTrait 的使用场景,它并不会感知类的其他属性和方法。
Reference:
–EOF–