Hyperf2.0 新功能早知道

李銘昕發表於2020-05-26

在使用 Hyperf1.1 的小夥伴們,通常都會碰到這麼一個問題,那就是協程上下文資料拷貝的問題。

比如我實現了一個 Listener,在監聽 SQL 的同時,會把當前請求的路由資料記錄下來。當出現慢查的時候,就可以精確定位到是哪個路由。

但當你以以下方式執行 SQL 時,可能就會出現這個錯誤 TypeError:Return value of Hyperf\HttpServer\Request::getRequest() must implement interface Psr\Http\Message\ServerRequestInterface, null returned

<?php

go(function(){
    Db::select("SELECT * FROM users;");
})

當然,你可以自己實現 go 方法,然後在 composer 載入之前提前載入這個檔案。例如

// 包含 go 方法的實現
require BASE_PATH . '/config/bootstrap.php';

require BASE_PATH . '/vendor/autoload.php';

但框架中直接使用 Coroutine::create 來建立協程就沒有辦法了,比如 ParallelConcurrent 等等

ClassMap 功能

而在 Hyperf2.0 版本中,框架實現了基於 composer class map 的替換功能,你可以自己實現 Hyperf\Utils\Coroutine,而框架會自動幫你替換掉原來的 Hyperf\Utils\Coroutine

<?php

declare(strict_types=1);
/**
 * This file is part of Hyperf.
 *
 * @link     https://www.hyperf.io
 * @document https://doc.hyperf.io
 * @contact  group@hyperf.io
 * @license  https://github.com/hyperf/hyperf/blob/master/LICENSE
 */
namespace Hyperf\Utils;

use App\Kernel\Context\Coroutine as BCoroutine;
use Swoole\Coroutine as SwooleCoroutine;

/**
 * @method static void defer(callable $callable)
 */
class Coroutine
{
    public static function __callStatic($name, $arguments)
    {
        if (! method_exists(SwooleCoroutine::class, $name)) {
            throw new \BadMethodCallException(sprintf('Call to undefined method %s.', $name));
        }
        return SwooleCoroutine::$name(...$arguments);
    }

    /**
     * Returns the current coroutine ID.
     * Returns -1 when running in non-coroutine context.
     */
    public static function id(): int
    {
        return SwooleCoroutine::getCid();
    }

    /**
     * Returns the parent coroutine ID.
     * Returns -1 when running in the top level coroutine.
     * Returns null when running in non-coroutine context.
     *
     * @see https://github.com/swoole/swoole-src/pull/2669/files#diff-3bdf726b0ac53be7e274b60d59e6ec80R940
     */
    public static function parentId(): ?int
    {
        $cid = SwooleCoroutine::getPcid();
        if ($cid === false) {
            return null;
        }

        return $cid;
    }

    /**
     * @return int Returns the coroutine ID of the coroutine just created.
     *             Returns -1 when coroutine create failed.
     */
    public static function create(callable $callable): int
    {
        return di()->get(BCoroutine::class)->create($callable);
    }

    public static function inCoroutine(): bool
    {
        return Coroutine::id() > 0;
    }
}

App\Kernel\Context\Coroutine 是實現了協程上下文拷貝功能的類。

測試

讓我們寫一段程式碼,進行測試。

<?php

declare(strict_types=1);
/**
 * This file is part of Hyperf.
 *
 * @link     https://www.hyperf.io
 * @document https://doc.hyperf.io
 * @contact  group@hyperf.io
 * @license  https://github.com/hyperf/hyperf/blob/master/LICENSE
 */
namespace App\Controller;

class IndexController extends Controller
{
    public function index()
    {
        $user = $this->request->input('user', 'Hyperf');
        $method = $this->request->getMethod();
        go(function () {
            var_dump($this->request->input('user'));
        });
        return $this->response->success([
            'user' => $user,
            'method' => $method,
            'message' => 'Hello Hyperf.',
        ]);
    }
}

當我們不使用 ClassMap 功能時,呼叫介面,會丟擲以下錯誤。

[WARNING] TypeError:Return value of Hyperf\HttpServer\Request::getRequest() must implement interface Psr\Http\Message\ServerRequestInterface, null returned(0) in /Users/limx/Applications/GitHub/hyperf/biz-skeleton/vendor/hyperf/http-server/src/Request.php:620
Stack trace:
#0 /Users/limx/Applications/GitHub/hyperf/biz-skeleton/vendor/hyperf/http-server/src/Request.php(579): Hyperf\HttpServer\Request->getRequest()
#1 /Users/limx/Applications/GitHub/hyperf/biz-skeleton/vendor/hyperf/utils/src/Functions.php(268): Hyperf\HttpServer\Request->Hyperf\HttpServer\{closure}()
#2 /Users/limx/Applications/GitHub/hyperf/biz-skeleton/vendor/hyperf/http-server/src/Request.php(593): call(Object(Closure))
#3 /Users/limx/Applications/GitHub/hyperf/biz-skeleton/vendor/hyperf/http-server/src/Request.php(587): Hyperf\HttpServer\Request->storeParsedData(Object(Closure))
#4 /Users/limx/Applications/GitHub/hyperf/biz-skeleton/vendor/hyperf/http-server/src/Request.php(97): Hyperf\HttpServer\Request->getInputData()
#5 /Users/limx/Applications/GitHub/hyperf/biz-skeleton/app/Controller/IndexController.php(21): Hyperf\HttpServer\Request->input('user')
#6 /Users/limx/Applications/GitHub/hyperf/biz-skeleton/vendor/hyperf/utils/src/Functions.php(268): App\Controller\IndexController->App\Controller\{closure}()
#7 /Users/limx/Applications/GitHub/hyperf/biz-skeleton/vendor/hyperf/utils/src/Coroutine.php(67): call(Object(Closure))
#8 {main}

當我們配置了 ClassMap 後,結果就正常了。

$ curl http://127.0.0.1:9501/\?user\=Hyperf
{"code":0,"data":{"user":"Hyperf","method":"GET","message":"Hello Hyperf."}}

string(6) "Hyperf"

可見 App\Kernel\Context\Coroutine 已被正確替換。

大家可以考慮一下,哪些場景,通過這個辦法可以輕鬆的解決呢?

寫在最後

Hyperf 是基於 Swoole 4.4+ 實現的高效能、高靈活性的 PHP 協程框架,內建協程伺服器及大量常用的元件,效能較傳統基於 PHP-FPM 的框架有質的提升,提供超高效能的同時,也保持著極其靈活的可擴充套件性,標準元件均基於 PSR 標準 實現,基於強大的依賴注入設計,保證了絕大部分元件或類都是 可替換 與 可複用 的。

框架元件庫除了常見的協程版的 MySQL 客戶端、Redis 客戶端,還為您準備了協程版的 Eloquent ORM、WebSocket 服務端及客戶端、JSON RPC 服務端及客戶端、GRPC 服務端及客戶端、Zipkin/Jaeger (OpenTracing) 客戶端、Guzzle HTTP 客戶端、Elasticsearch 客戶端、Consul 客戶端、ETCD 客戶端、AMQP 元件、Apollo 配置中心、阿里雲 ACM 應用配置管理、ETCD 配置中心、基於令牌桶演算法的限流器、通用連線池、熔斷器、Swagger 文件生成、Swoole Tracker、Blade 和 Smarty 檢視引擎、Snowflake 全域性ID生成器 等元件,省去了自己實現對應協程版本的麻煩。

Hyperf 還提供了 基於 PSR-11 的依賴注入容器、註解、AOP 面向切面程式設計、基於 PSR-15 的中介軟體、自定義程式、基於 PSR-14 的事件管理器、Redis/RabbitMQ 訊息佇列、自動模型快取、基於 PSR-16 的快取、Crontab 秒級定時任務、Translation 國際化、Validation 驗證器 等非常便捷的功能,滿足豐富的技術場景和業務場景,開箱即用。%

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

Any fool can write code that a computer can understand. Good programmers write code that humans can understand.

相關文章