如何靈活使用 Hyperf dependencies 配置

李銘昕發表於2020-01-19

倉庫地址

替換某個類的實現

讓我們實現一個十分簡單的 Service,程式碼如下

<?php

namespace App\Service;

class DemoService
{
    public function say(): string
    {
        return 'I am in ' . static::class;
    }
}

然後控制器中呼叫,並返回資料

public function say()
{
    return $this->response->success(
        $this->container->get(DemoService::class)->say()
    );
}

訪問介面,並檢視結果

$ curl http://127.0.0.1:9501/index/say
{"code":0,"data":"I am in App\\Service\\DemoService"}

接下來我們重新寫一個 Demo2Service

<?php

namespace App\Service;

class Demo2Service
{
    public function say(): string
    {
        return 'I am not in ' . DemoService::class;
    }
}

然後修改 dependencies.php 配置。

<?php

use App\Service;

return [
    Service\DemoService::class => Service\Demo2Service::class,
];

訪問介面,並檢視結果

$ curl http://127.0.0.1:9501/index/say
{"code":0,"data":"I am not in App\\Service\\DemoService"}

可見 DemoService 會被直接替換成 Demo2Service

活用 Factory

有時候我們需要使用多個 Middleware,雖然邏輯大致一樣,但可能還是有一部分差別,這個時候透過上述方式,就顯得有些單薄了,畢竟我需要兩個都要。

讓我們寫一個簡單的 AuthMiddlewareUserAuth

<?php

namespace App\Middleware;

use App\Service\UserAuth;
use Psr\Container\ContainerInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;

class AuthMiddleware implements MiddlewareInterface
{
    /**
     * @var ContainerInterface
     */
    protected $container;

    /**
     * @var string
     */
    protected $pool;

    public function __construct(ContainerInterface $container, string $pool)
    {
        $this->container = $container;
        $this->pool = $pool;
    }

    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
    {
        // TODO: 根據 pool 查詢不通的 Redis 等儲存引擎 並驗證許可權

        // 將對應的許可權放到 UserAuth 中,這裡模式傳入一個 object,實際可以傳入 Model 等。
        UserAuth::instance()->load((object) [
            'pool' => $this->pool,
        ]);

        return $handler->handle($request);
    }
}
<?php

namespace App\Service;

use Hyperf\Utils\Traits\StaticInstance;

class UserAuth
{
    use StaticInstance;

    protected $user;

    public function load(object $user)
    {
        $this->user = $user;
    }
}

這個時候,如果存在兩個 server,如果只有一份 AuthMiddleware,顯然是無法滿足的。比如以下配置

<?php

use Hyperf\Server\Server;
use Hyperf\Server\SwooleEvent;

return [
    'mode' => SWOOLE_BASE,
    'servers' => [
        [
            'name' => 'http',
            'type' => Server::SERVER_HTTP,
            'host' => '0.0.0.0',
            'port' => 9501,
            'sock_type' => SWOOLE_SOCK_TCP,
            'callbacks' => [
                SwooleEvent::ON_REQUEST => [Hyperf\HttpServer\Server::class, 'onRequest'],
            ],
        ],
        [
            'name' => 'admin',
            'type' => Server::SERVER_HTTP,
            'host' => '0.0.0.0',
            'port' => 9502,
            'sock_type' => SWOOLE_SOCK_TCP,
            'callbacks' => [
                SwooleEvent::ON_REQUEST => ['AdminServer', 'onRequest'],
            ],
        ],
    ],
    'settings' => [
        'enable_coroutine' => true,
        'worker_num' => 4,
        'pid_file' => BASE_PATH . '/runtime/hyperf.pid',
        'open_tcp_nodelay' => true,
        'max_coroutine' => 100000,
        'open_http2_protocol' => true,
        'max_request' => 100000,
        'socket_buffer_size' => 2 * 1024 * 1024,
    ],
    'callbacks' => [
        SwooleEvent::ON_BEFORE_START => [Hyperf\Framework\Bootstrap\ServerStartCallback::class, 'beforeStart'],
        SwooleEvent::ON_WORKER_START => [Hyperf\Framework\Bootstrap\WorkerStartCallback::class, 'onWorkerStart'],
        SwooleEvent::ON_PIPE_MESSAGE => [Hyperf\Framework\Bootstrap\PipeMessageCallback::class, 'onPipeMessage'],
    ],
];

這個時候,我們使用 Factory 模式來很方便的將一份 AuthMiddleware 變成兩份。

讓我們建立兩個 Factory

namespace App\Middleware\Factory;

use App\Middleware\AuthMiddleware;
use Psr\Container\ContainerInterface;

class AuthMiddlewareFactory
{
    public function __invoke(ContainerInterface $container)
    {
        return new AuthMiddleware($container, 'user');
    }
}

class AdminAuthMiddlewareFactory
{
    public function __invoke(ContainerInterface $container)
    {
        return new AuthMiddleware($container, 'admin');
    }
}

然後配置 dependencies.php 配置

<?php

use App\Middleware;
use App\Service;

return [
    // Hyperf\Contract\StdoutLoggerInterface::class => App\Kernel\Log\LoggerFactory::class,
    Service\DemoService::class => Service\Demo2Service::class,
    'AdminServer' => Hyperf\HttpServer\Server::class,
    'UserAuthMiddleware' => Middleware\Factory\AuthMiddlewareFactory::class,
    'AdminUserAuthMiddleware' => Middleware\Factory\AdminAuthMiddlewareFactory::class,
];

然後修改 middlewares.php 配置

<?php

return [
    'http' => [
        'UserAuthMiddleware',
    ],
    'admin' => [
        'AdminUserAuthMiddleware',
    ],
];

接下來修改我們的介面。

public function pool()
{
    $user = UserAuth::instance()->getUser();

    return $this->response->success($user->pool);
}

測試

$ curl http://127.0.0.1:9501/index/pool
{"code":0,"data":"user"}

$ curl http://127.0.0.1:9502/index/pool
{"code":0,"data":"admin"}

當然,如果覺得寫兩個 Factory 比較繁瑣,也可以使用更加方便的匿名函式

修改我們的 dependencies.php 配置如下

<?php

use App\Middleware;
use App\Service;

return [
    // Hyperf\Contract\StdoutLoggerInterface::class => App\Kernel\Log\LoggerFactory::class,
    Service\DemoService::class => Service\Demo2Service::class,
    'AdminServer' => Hyperf\HttpServer\Server::class,
    // 'UserAuthMiddleware' => Middleware\Factory\AuthMiddlewareFactory::class,
    // 'AdminUserAuthMiddleware' => Middleware\Factory\AdminAuthMiddlewareFactory::class,
    'UserAuthMiddleware' => function () {
        return make(Middleware\AuthMiddleware::class, ['pool' => 'user']);
    },
    'AdminUserAuthMiddleware' => function () {
        return make(Middleware\AuthMiddleware::class, ['pool' => 'admin']);
    },
];

高階用法

接下來我們透過以上方式,修改一下框架裡的類,這樣可以在不修改原始碼的情況下,進行某些類的替換

比如,讓我們修改一下 404 的返回。

<?php

namespace App\Kernel\Http;

use Hyperf\HttpMessage\Stream\SwooleStream;
use Hyperf\HttpServer;
use Psr\Http\Message\ServerRequestInterface;

class CoreMiddleware extends HttpServer\CoreMiddleware
{
    protected function handleNotFound(ServerRequestInterface $request)
    {
        return $this->response()->withStatus(404)->withBody(new SwooleStream('Not Found.'));
    }
}

首先,在不替換的情況下,讓我們訪問一個不存在的路由

$ curl http://127.0.0.1:9501/index/not-found -i
HTTP/1.1 404 Not Found
Server: Hyperf
Connection: keep-alive
Content-Type: text/html
Date: Sun, 19 Jan 2020 06:53:36 GMT
Content-Length: 0

接下來我們修改 dependencies.php 配置

<?php

use App\Kernel\Http;
use App\Middleware;
use App\Service;

return [
    // Hyperf\Contract\StdoutLoggerInterface::class => App\Kernel\Log\LoggerFactory::class,
    Service\DemoService::class => Service\Demo2Service::class,
    'AdminServer' => Hyperf\HttpServer\Server::class,
    // 'UserAuthMiddleware' => Middleware\Factory\AuthMiddlewareFactory::class,
    // 'AdminUserAuthMiddleware' => Middleware\Factory\AdminAuthMiddlewareFactory::class,
    'UserAuthMiddleware' => function () {
        return make(Middleware\AuthMiddleware::class, ['pool' => 'user']);
    },
    'AdminUserAuthMiddleware' => function () {
        return make(Middleware\AuthMiddleware::class, ['pool' => 'admin']);
    },
    Hyperf\HttpServer\CoreMiddleware::class => Http\CoreMiddleware::class,
];

然後再讓我們訪問試一下

$ curl http://127.0.0.1:9501/index/not-found -i
HTTP/1.1 404 Not Found
Server: Hyperf
Connection: keep-alive
Content-Type: text/html
Date: Sun, 19 Jan 2020 06:54:19 GMT
Content-Length: 10

Not Found.

可以看到,我們已經成功替換了對應的 CoreMiddleware

寫在最後

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.

相關文章