[Laravel] 客製化你的Laravel: macro

本文最後更新於:2024年5月2日 晚上

此文章同步刊登於哲煜科技的Medium

前言

有的時候,會希望在Laravel原生的class新增功能的時候,大多我們都會寫一個新的class並繼承,但是其實Laravel提供了一個不同的方式,讓我們可以在常用的class上,直接新增想要的function,那就是macro。

一、使用

我們可以在Provider的boot()裡面使用macro來添加自訂的function,這邊以Collection為例子:

Collection::macro('remove', function (string $filterValue, ?string $filterKey = null) {
    foreach ($this as $key => $item) {
        // 根據是否有$filterKey決定如何取值
        $value = is_null($filterKey) ? $item : data_get($item, $filterKey);
        // 如果值與$filterValue相同就移除該item
        if ($value == $filterValue) {
            $this->forget($key);
        }
    }

    return $this;
});

現在Collection就被註冊了一個名為remove的function了,可以直接透過value移除指定的元素:

$collection = collect([
    '標題1', '標題2', '標題3', '標題4'
]);

$collection->remove('標題3');

或是像data_get一樣取得巢狀物件或陣列的方式,移除指定的元素,例如:

$collection = collect([
[
    'title' => '標題1',
    'content' => '內容1',
    'writer' => [
        'name' => '作者1',
        'gender' => 'man',
    ],
],
[
    'title' => '標題2',
    'content' => '內容2',
    'writer' => [
        'name' => '作者2',
        'gender' => 'woman',
    ],
],
[
    'title' => '標題3',
    'content' => '內容3',
    'writer' => [
        'name' => '作者3',
            'gender' => 'man',
        ],
    ],
]);

$collection->remove('作者3', 'writer.name');

二、實現方式

在Laravel中只要是有使用Illuminate\Support\Traits\Macroable這個trait的class,就可以使用macro,而Laravel是如何實現這個功能的呢?其實也很簡單,就是利用魔術方法中的call function,讓我們看一下原始碼寫了一些甚麼吧。

public static function macro($name, $macro)
{
    static::$macros[$name] = $macro;
}

macro做的事情非常單純,就是把我們放進來的function name跟Closure儲存下來,而等到我們使用自訂function的時候就會觸發call function的機制:

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

這樣我們的自訂functin就註冊給了這個class,也就可以像內建的function來使用了。
在Laravel中,有使用Macroable的class非常的多,包括但不限於以下:

  • Response
  • Request
  • Collection
  • HTML
  • Form
  • Str
  • Arr
  • Translator
  • File
  • Lang

但凡只要有引用Macroable的calss都可以使用carce來註冊自己的function,筆者這邊在Laravel 8內搜尋了一下就發現有51個class有引用Macroable,這還不包含這些class的子class,可以說非常的廣泛,大家不妨可以自己試試看。

參考資料:


[Laravel] 客製化你的Laravel: macro
https://hankz1108.github.io/posts/20220825-laravel-macro/
作者
Hankz
發布於
2022年8月25日
更新於
2024年5月2日
許可協議