突發奇想:用 PHPDoc 和 反射 來給 PHP 整一個類似 Python 的裝飾器~~

largezhou發表於2020-04-07

裝飾器簡單解釋:把一個函式傳給裝飾器,並返回一個函式,替換掉原函式。

舉個 Python 的栗子:

import time


def time_recorder(fn):
    def wrapper():
        start = time.time()
        fn()
        print(time.time() - start)

    return wrapper


@time_recorder
def test():
    time.sleep(1)
    print('test')


# 上面的裝飾器用法,相當於下面的語句
# test = time_recorder(test)

test()

輸出:

test
1.00072383881

在 Laravel 中,我們就在控制器中嘗試這個吧

首先,重寫控制器的 callAction

// 獲取註釋中的各種引數以及對應的值
// 反射居然不能直接獲取到各個引數,只能獲取到整個註釋的字串
// 直接 copy 一個:https://tomlankhorst.nl/get-phpdoc-parameters-of-a-method/
public function phpdocParams(\ReflectionMethod $method): array
{
    // Retrieve the full PhpDoc comment block
    $doc = $method->getDocComment();

    if ($doc === false) {
        return [];
    }

    // Trim each line from space and star chars
    $lines = array_map(function ($line) {
        return trim($line, " *");
    }, explode("\n", $doc));

    // Retain lines that start with an @
    $lines = array_filter($lines, function ($line) {
        return strpos($line, "@") === 0;
    });

    $args = [];

    // Push each value in the corresponding @param array
    foreach ($lines as $line) {
        [$param, $value] = explode(' ', $line, 2);
        $args[$param][] = $value;
    }

    return $args;
}

public function callAction($method, $parameters)
{
    $methodReflection = new \ReflectionMethod(static::class, $method);
    $decorators = $this->phpdocParams($methodReflection)['@decorator'] ?? [];
    $decorators = array_reverse($decorators);

    $fn = [$this, $method];
    foreach ($decorators as $decorator) {
        $fn = call_user_func($decorator, $fn);
    }

    return call_user_func_array($fn, $parameters);
}

定義兩個全域性輔助函式隨便試試:

function my_logger($fn)
{
    return function () use ($fn) {
        Log::info('start');
        $res = $fn(...func_get_args());
        Log::info('end');
        // 這裡不搞個 return,會拿不到控制器的返回結果~~
        return $res;
    };
}

function time_recorder($fn)
{
    return function () use ($fn) {
        $start = microtime(true);
        $res = $fn(...func_get_args());
        Log::info('time spent: '.(microtime(true) - $start));
        return $res;
    };
}

在控制中使用裝飾器:

/**
 * @param Request $request
 *
 * 這裡用了兩個裝飾器
 * 包裹(裝飾)順序是從內(下)往外(上),執行順序是從外(上)往內(下)
 *
 * @decorator my_logger
 * @decorator time_recorder
 *
 * @return mixed
 */
public function index(Request $request)
{
    return response()->json($request->input());
}

瀏覽器訪問:xx.xx.xx/some-route?a=1&b=2

瀏覽器的結果:{"a":"1","b":"2"}

log 的結果:

[2020-04-07 08:50:47] local.INFO: start  
[2020-04-07 08:50:47] local.INFO: time spent: 0.013785123825073  
[2020-04-07 08:50:47] local.INFO: end  

其實有點像中介軟體~~

本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章