百度百科的定義:
電腦科學裡的巨集(Macro),是一種批量處理的稱謂。一般說來,巨集是一種規則或模式,或稱語法替換 ,用於說明某一特定輸入(通常是字串)如何根據預定義的規則轉換成對應的輸出(通常也是字串)。這種替換在預編譯時進行,稱作巨集展開。
- 我一開始接觸巨集是在大學上計算機基礎課程時,老師講office時說的。那時老師介紹巨集操作時沒太在意,只記得這一操作很強大,它能使日常工作變得更容易。
- 今天我們講講Laravel中的巨集操作
首先完整的原始碼
<?php
namespace IlluminateSupportTraits;
use Closure;
use ReflectionClass;
use ReflectionMethod;
use BadMethodCallException;
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
*/
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("Method {$method} does not exist.");
}
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("Method {$method} does not exist.");
}
$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);
}
}
-
Macroable::macro
方法
public static function macro($name, $macro)
{
static::$macros[$name] = $macro;
}
很簡單的程式碼,根據引數的註釋,$macro
可以傳一個閉包或者物件,之所以可以傳物件,多虧了PHP中的魔術方法
class Father
{
// 通過增加魔術方法**__invoke**我們就可以把物件當做閉包來使用了。
public function __invoke()
{
echo __CLASS__;
}
}
class Child
{
use IlluminateSupportTraitsMacroable;
}
// 增加了巨集指令之後,我們就能呼叫 Child 物件中不存在的方法了
Child::macro(`show`, new Father);
// 輸出:Father
(new Child)->show();
-
Macroable::mixin
方法
這個方法是把一個物件的方法的返回結果注入到原物件中
public static function mixin($mixin)
{
// 通過反射獲取該物件中所有公開和受保護的方法
$methods = (new ReflectionClass($mixin))->getMethods(
ReflectionMethod::IS_PUBLIC | ReflectionMethod::IS_PROTECTED
);
foreach ($methods as $method) {
// 設定方法可訪問,因為受保護的不能在外部呼叫
$method->setAccessible(true);
// 呼叫 macro 方法批量建立巨集指令
static::macro($method->name, $method->invoke($mixin));
}
}
// 實際使用
class Father
{
public function say()
{
return function () {
echo `say`;
};
}
public function show()
{
return function () {
echo `show`;
};
}
protected function eat()
{
return function () {
echo `eat`;
};
}
}
class Child
{
use IlluminateSupportTraitsMacroable;
}
// 批量繫結巨集指令
Child::mixin(new Father);
$child = new Child;
// 輸出:say
$child->say();
// 輸出:show
$child->show();
// 輸出:eat
$child->eat();
在上面的程式碼可以看出mixin
可以將一個類的方法繫結到巨集類中。需要注意的就是,方法必須是返回一個閉包型別。
-
Macroable::hasMacro
方法
public static function hasMacro($name)
{
return isset(static::$macros[$name]);
}
這個方法就比較簡單沒什麼複雜可言,就判斷是否存在巨集指令。通常是使用巨集指令之前判斷一下。
-
Macroable::__call
和Macroable::__callStatic
方法
正是由於這兩個方法,我們才能進行巨集操作,兩個方法除了執行方式不同,程式碼大同小異。這裡講一下__call
public function __call($method, $parameters)
{
// 如果不存在這個巨集指令,直接丟擲異常
if (! static::hasMacro($method)) {
throw new BadMethodCallException("Method {$method} does not exist.");
}
// 得到儲存的巨集指令
$macro = static::$macros[$method];
// 閉包做一點點特殊的處理
if ($macro instanceof Closure) {
return call_user_func_array($macro->bindTo($this, static::class), $parameters);
}
// 不是閉包,比如物件的時候,直接通過這種方法執行,但是要確保物件有`__invoke`方法
return call_user_func_array($macro, $parameters);
}
class Child
{
use IlluminateSupportTraitsMacroable;
protected $name = `father`;
}
// 閉包的特殊處理,需要做的就是繫結 $this, 如
Child::macro(`show`, function () {
echo $this->name;
});
// 輸出:father
(new Child)->show();
在上面的操作中我們繫結巨集時,在閉包中可以通過$this
來呼叫Child
的屬性,是因為在__call
方法中我們使用Closure::bindTo
方法。
官網對
Closure::bindTo
的解釋:複製當前閉包物件,繫結指定的$this物件和類作用域。
Laravel 中對類增加巨集指令
Laravel中很多類都使用了巨集這個trait
比如IlluminateFilesystemFilesystem::class
,我們想為這個類增加一個方法,但不會動到裡面的程式碼。
- 我們只需要到
AppProvidersAppServiceProvider::register
方法增加巨集指令(你也可以專門新建一個服務提供者專門處理)