前言
簡單分析了一下Discuz! Q 的原始碼,純屬個人學習,不對的地方望指正哈。
用到的laravel元件
在vendor/discuz/core/composer.json中可以看到
- “illuminate/container”: ioc服務容器
- “illuminate/database”: 資料庫
- “illuminate/bus”: 事件匯流排
- “illuminate/events”: 事件
- “illuminate/config”: 配置服務
- “illuminate/view”: 檢視
- “illuminate/session”: session
- 路由沒有用laravel,而是”nikic/fast-route”
- 中介軟體沒有用laravel,而是”laminas/laminas-stratigility”,遵循 PSR-7 HTTP 規範
- 處理請求用的”laminas/laminas-httphandlerrunner”,遵循 PSR-15 HTTP 處理器規範
入口檔案分析
在public/index.php中
- $app = new Discuz\Foundation\Application(dirname(__DIR__)); //例項化一個容器,並繫結了事件服務和別名繫結
- $app->make(Discuz\Http\Server::class)->listen(); //例項化Server,並處理請求(重心在這裡)
Discuz\Http\Server::class 分析
public function listen()
{
// 這裡繫結了核心的服務類,包括ApiServiceProvider、WebServiceProvider,這兩個Provider會載入routes/下的路由配置和註冊中介軟體
$this->siteBoot();
$pipe = new MiddlewarePipe();
//new RequestHandler裡用到了ApiServiceProvider、WebServiceProvider註冊的中介軟體
$pipe->pipe(new RequestHandler([
'/api' => 'discuz.api.middleware',
'/' => 'discuz.web.middleware'
], $this->app));
$psr17Factory = new Psr17Factory();
$request = (new ServerRequestCreator($psr17Factory, $psr17Factory, $psr17Factory, $psr17Factory))->fromGlobals();
$this->app->instance('request', $request);
$this->app->alias('request', ServerRequestInterface::class);
// 上面的例項化的中介軟體物件和Request物件都注入到RequestHandlerRunner
$runner = new RequestHandlerRunner(
$pipe,
new SapiEmitter,
function () use ($request) {
return $request;
},
function (Throwable $e) {
$generator = new ErrorResponseGenerator;
return $generator($e, new ServerRequest, new Response);
}
);
// run方法有兩個重要步驟:1. 處理請求$this->handler->handle($request);2. 收尾工作$this->emitter->emit($response);
$runner->run();
}
$this->handler->handle($request) 在 Laminas\Stratigility\MiddlewarePipe
- 主要是中介軟體的處理流程,利用SplQueue的先進先出佇列來實現,在ApiServiceProvider、WebServiceProvider中註冊了中介軟體,enqueue進SplQueue佇列,然後一個一個的dequeue出佇列並處理請求。
- 最後一箇中介軟體是DispatchRoute::class,它則是用來根據請求的url找到對應的控制器開始業務處理。
- PS:中介軟體的處理不再像laravel那樣用到array_reduce的特性,我寫了一個簡單的demo模擬了下:
class Next
{
private $fallbackHandler;
private $queue;
public function __construct($queue, $fallbackHandler)
{
$this->queue = clone $queue;
$this->fallbackHandler = $fallbackHandler;
}
public function handle($request)
{
if ($this->queue->isEmpty()) {
$this->queue = null;
$fallbackHandler = $this->fallbackHandler;
return $fallbackHandler($request);
}
$middleware = $this->queue->dequeue();
$next = clone $this;
$this->queue = null;
return $middleware($request, $next);
}
}
class SplQueuePipeline
{
private $pipeline;
public function __construct()
{
$this->pipeline = new SplQueue();
}
public function __clone()
{
$this->pipeline = clone $this->pipeline;
}
public function handle($request)
{
return $this->process($request, function ($r) {
return $r;
});
}
public function process($request, $handler)
{
return (new Next($this->pipeline, $handler))->handle($request);
}
public function pipe($middleware)
{
$this->pipeline->enqueue($middleware);
}
}
$pipe = new SplQueuePipeline();
$pipe->pipe(function ($request, $next) {
var_dump("pipe 1");
return $next->handle($request);
});
$pipe->pipe(function ($request, $next) {
var_dump("pipe 2");
return $next->handle($request);
});
$rs = $pipe->handle("request");
var_dump($rs);
疑問:app\Api\Middleware目錄下的中介軟體是如何生效的
- App\Listeners\AddApiMiddleware 將 app\Api\Middleware 目錄的中介軟體進行註冊。
- App\Providers\EventServiceProvider 註冊了 Discuz\Api\Events\ConfigMiddleware 事件的監聽,listener 就是 App\Listeners\AddApiMiddleware。
- App\Providers\EventServiceProvider 定義在配置檔案的 providers 陣列內。而這些provider 的註冊在上面提到的$this->siteBoot();中完成。
- 那 ConfigMiddleware 事件又是怎麼產生的呢?在上面提到的 ApiServiceProvider 中,註冊完系統中介軟體後會觸發 ConfigMiddleware 事件。這個流程我模擬了一個簡單的demo如下:
class EventDispatcher
{
private $bindings = [];
public function listen($eventClass, $listenerClass)
{
if (empty($this->bindings[$eventClass])) {
$this->bindings[$eventClass] = [];
}
$this->bindings[$eventClass][] = $listenerClass;
}
public function dispatch($obj)
{
$eventClass = get_class($obj);
if (!empty($this->bindings[$eventClass])) {
foreach ($this->bindings[$eventClass] as $listenerClass) {
$reflector = new ReflectionClass($listenerClass);
$instance = $reflector->newInstance();
$method = $reflector->getMethod('handle');
$method->invoke($instance, $obj);
}
}
}
}
class AEvent
{
public $pipe;
public function __construct($obj)
{
$this->pipe = $obj;
}
}
class AListener
{
public function handle(AEvent $obj)
{
$obj->pipe->arr[] = "pipe 2";
}
}
$obj = new stdClass();
$obj->arr = ['pipe 1'];
$eventDispatcher = new EventDispatcher();
$eventDispatcher->listen(AEvent::class, AListener::class);
$eventDispatcher->dispatch(new AEvent($obj));
print_r($obj->arr);
本作品採用《CC 協議》,轉載必須註明作者和本文連結