HttpKernel 組件
HttpKernel 組件透過使用 EventDispatcher 組件,提供結構化的流程,將
Request
轉換為Response
。它具有足夠的彈性來建立完整堆疊的框架 (Symfony) 或進階的 CMS (Drupal)。
安裝
1
$ composer require symfony/http-kernel
注意
如果您在 Symfony 應用程式之外安裝此組件,您必須在程式碼中引入 vendor/autoload.php
檔案,以啟用 Composer 提供的類別自動載入機制。閱讀這篇文章以瞭解更多細節。
請求-回應生命週期
另請參閱
本文說明如何在任何 PHP 應用程式中將 HttpKernel 功能作為獨立組件使用。在 Symfony 應用程式中,一切都已設定完成並可直接使用。閱讀控制器和事件和事件監聽器文章,以瞭解如何在 Symfony 應用程式中使用它來建立控制器和定義事件。
每個 HTTP 網路互動都始於請求,終於回應。身為開發人員,您的工作是建立 PHP 程式碼,讀取請求資訊(例如 URL),並建立和傳回回應(例如 HTML 頁面或 JSON 字串)。這是 Symfony 應用程式中請求-回應生命週期的簡化概述
- 使用者在瀏覽器中請求資源;
- 瀏覽器將請求傳送至伺服器;
- Symfony 給予應用程式一個
Request
物件; - 應用程式使用
Request
物件的資料產生Response
物件; - 伺服器將回應傳回給瀏覽器;
- 瀏覽器向使用者顯示資源。
通常,會建置某種框架或系統來處理所有重複性任務(例如路由、安全性等),以便開發人員可以建置應用程式的每個頁面。這些系統如何建置差異很大。HttpKernel 組件提供了一個介面,將從請求開始並建立適當回應的過程形式化。此組件旨在成為任何應用程式或框架的核心,無論該系統的架構如何變化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
namespace Symfony\Component\HttpKernel;
use Symfony\Component\HttpFoundation\Request;
interface HttpKernelInterface
{
// ...
/**
* @return Response A Response instance
*/
public function handle(
Request $request,
int $type = self::MAIN_REQUEST,
bool $catch = true
): Response;
}
在內部,HttpKernel::handle() - HttpKernelInterface::handle() 的具體實作 - 定義了一個生命週期,該生命週期始於 Request 並終於 Response。
此生命週期的確切細節是了解核心(以及 Symfony 框架或任何其他使用核心的程式庫)如何運作的關鍵。
HttpKernel:由事件驅動
HttpKernel::handle()
方法透過分發事件在內部運作。這使得該方法既靈活又有點抽象,因為使用 HttpKernel 建置的框架/應用程式的所有「工作」實際上都是在事件監聽器中完成的。
為了幫助解釋這個過程,本文將著眼於過程的每個步驟,並討論 HttpKernel 的一個特定實作 - Symfony 框架 - 如何運作。
最初,使用 HttpKernel 不需要太多步驟。您建立一個事件分發器和一個控制器和參數解析器(如下所述)。為了完成您的運作核心,您將為下面討論的事件新增更多事件監聽器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
use Symfony\Component\EventDispatcher\EventDispatcher;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpKernel\Controller\ArgumentResolver;
use Symfony\Component\HttpKernel\Controller\ControllerResolver;
use Symfony\Component\HttpKernel\HttpKernel;
// create the Request object
$request = Request::createFromGlobals();
$dispatcher = new EventDispatcher();
// ... add some event listeners
// create your controller and argument resolvers
$controllerResolver = new ControllerResolver();
$argumentResolver = new ArgumentResolver();
// instantiate the kernel
$kernel = new HttpKernel($dispatcher, $controllerResolver, new RequestStack(), $argumentResolver);
// actually execute the kernel, which turns the request into a response
// by dispatching events, calling a controller, and returning the response
$response = $kernel->handle($request);
// send the headers and echo the content
$response->send();
// trigger the kernel.terminate event
$kernel->terminate($request, $response);
請參閱「完整的運作範例」以取得更具體的實作。
有關將監聽器新增至以下事件的一般資訊,請參閱建立事件監聽器。
另請參閱
有一個關於使用 HttpKernel 組件和其他 Symfony 組件來建立您自己的框架的精彩教學系列。請參閱簡介。
1) kernel.request
事件
典型用途:向 Request
新增更多資訊、初始化系統的各個部分,或在可能時傳回 Response
(例如,拒絕存取的安全性層)。
在 HttpKernel::handle 內部分發的第一個事件是 kernel.request
,它可能具有各種不同的監聽器。
此事件的監聽器可能非常多樣化。某些監聽器(例如安全性監聽器)可能具有足夠的資訊來立即建立 Response
物件。例如,如果安全性監聽器判斷使用者沒有存取權,則該監聽器可能會傳回 RedirectResponse 至登入頁面或 403 拒絕存取回應。
如果在此階段傳回 Response
,則程序將直接跳至 kernel.response 事件。
其他監聽器會初始化事物或向請求新增更多資訊。例如,監聽器可能會判斷並在 Request
物件上設定語言環境。
另一個常見的監聽器是路由。路由器監聽器可能會處理 Request
並判斷應呈現的控制器(請參閱下一節)。實際上,Request
物件具有「屬性」包,這是儲存有關請求的額外應用程式特定資料的完美位置。這表示如果您的路由器監聽器以某種方式判斷控制器,則它可以將其儲存在 Request
屬性上(您的控制器解析器可以使用它)。
總體而言,kernel.request
事件的目的是直接建立並傳回 Response
,或向 Request
新增資訊(例如設定語言環境或在 Request
屬性上設定其他資訊)。
注意
為 kernel.request
事件設定回應時,傳播會停止。這表示優先權較低的監聽器將不會執行。
2) 解析控制器
假設沒有 kernel.request
監聽器能夠建立 Response
,則 HttpKernel 中的下一步是判斷和準備(即解析)控制器。控制器是最終應用程式程式碼的一部分,負責為特定頁面建立和傳回 Response
。唯一的要求是它是 PHP 可呼叫物件 - 即函式、物件上的方法或 Closure
。
但是,如何判斷請求的確切控制器完全取決於您的應用程式。這是「控制器解析器」的工作 - 一個實作 ControllerResolverInterface 的類別,並且是 HttpKernel
的建構函式引數之一。
您的工作是建立一個實作介面的類別,並填寫其方法:getController()
。實際上,已經存在一個預設實作,您可以直接使用或從中學習:ControllerResolver。此實作在下面的側邊欄中進行了更多說明
1 2 3 4 5 6 7 8
namespace Symfony\Component\HttpKernel\Controller;
use Symfony\Component\HttpFoundation\Request;
interface ControllerResolverInterface
{
public function getController(Request $request): callable|false;
}
在內部,HttpKernel::handle()
方法首先在控制器解析器上呼叫 getController()。此方法會傳遞 Request
,並負責以某種方式根據請求的資訊判斷並傳回 PHP 可呼叫物件(控制器)。
3) kernel.controller
事件
典型用途:初始化事物或在控制器執行之前變更控制器。
在判斷控制器可呼叫物件後,HttpKernel::handle()
會分發 kernel.controller
事件。此事件的監聽器可能會初始化系統的某些部分,這些部分需要在某些事物(例如控制器、路由資訊)已判斷但控制器尚未執行之後初始化。
此事件的另一個典型用例是使用 getAttributes() 方法從控制器擷取屬性。有關一些範例,請參閱下面的 Symfony 區段。
此事件的監聽器也可以透過在傳遞給此事件監聽器的事件物件上呼叫 ControllerEvent::setController 來完全變更控制器可呼叫物件。
4) 取得控制器參數
接下來,HttpKernel::handle()
呼叫 ArgumentResolverInterface::getArguments()。請記住,在 getController()
中傳回的控制器是可呼叫物件。getArguments()
的目的是傳回應傳遞給該控制器的引數陣列。雖然內建的 ArgumentResolver 是一個很好的範例,但確切的做法完全取決於您的設計。
此時,核心具有 PHP 可呼叫物件(控制器)和在執行該可呼叫物件時應傳遞的引數陣列。
5) 呼叫控制器
HttpKernel::handle()
的下一步是執行控制器。
控制器的職責是為給定的資源建置回應。這可以是 HTML 頁面、JSON 字串或任何其他內容。與到目前為止程序的每個其他部分不同,此步驟由「最終開發人員」針對建置的每個頁面實作。
通常,控制器會回傳一個 Response
物件。如果這是真的,那麼 Kernel 的工作就差不多完成了!在這種情況下,下一步是 kernel.response 事件。
但是,如果控制器回傳的不是 Response
,那麼 Kernel 還有更多工作要做 - kernel.view (因為最終目標是始終產生一個 Response
物件)。
注意
控制器必須回傳某些東西。如果控制器回傳 null
,將會立即拋出例外。
6) kernel.view
事件
典型用途:將來自控制器的非 Response
回傳值轉換為 Response
如果控制器沒有回傳 Response
物件,那麼 Kernel 會分派另一個事件 - kernel.view
。此事件的監聽器的工作是使用控制器的回傳值(例如,資料陣列或物件)來建立一個 Response
。
如果您想使用「視圖」層,這會很有用:您可以從控制器回傳代表頁面的資料,而不是回傳 Response
。此事件的監聽器接著可以使用這些資料來建立格式正確的 Response
(例如 HTML、JSON 等)。
在這個階段,如果沒有監聽器在此事件上設定回應,則會拋出例外:控制器或其中一個視圖監聽器必須始終回傳 Response
。
注意
當為 kernel.view
事件設定回應時,傳播會停止。這表示優先順序較低的監聽器將不會被執行。
7) kernel.response
事件
典型用途:在 Response
物件傳送之前修改它
Kernel 的最終目標是將 Request
轉換為 Response
。Response
可能在 kernel.request 事件期間建立,從 控制器 回傳,或由 kernel.view 事件的監聽器之一回傳。
無論誰建立 Response
,另一個事件 - kernel.response
會在之後立即分派。此事件的典型監聽器將以某種方式修改 Response
物件,例如修改標頭、新增 Cookie,甚至變更 Response
本身的內容(例如,在 HTML 回應的結尾 </body>
標籤之前注入一些 JavaScript)。
在此事件分派之後,最終的 Response
物件會從 handle() 回傳。在最典型的用例中,您可以接著呼叫 send() 方法,該方法會傳送標頭並印出 Response
內容。
8) kernel.terminate
事件
典型用途:在回應串流傳輸到使用者後執行一些「繁重」的操作
HttpKernel 流程的最後一個事件是 kernel.terminate
,它是獨特的,因為它發生在 HttpKernel::handle()
方法之後,以及在回應傳送給使用者之後。回想一下上面的內容,然後使用 Kernel 的程式碼會像這樣結束
1 2 3 4 5
// sends the headers and echoes the content
$response->send();
// triggers the kernel.terminate event
$kernel->terminate($request, $response);
如您所見,透過在傳送回應後呼叫 $kernel->terminate
,您將觸發 kernel.terminate
事件,您可以在其中執行某些您可能延遲的操作,以便盡快將回應回傳給用戶端(例如,傳送電子郵件)。
警告
在內部,HttpKernel 使用了 fastcgi_finish_request PHP 函數。這表示目前只有 PHP FPM 伺服器 API 能夠在伺服器的 PHP 處理程序仍在執行某些任務時將回應傳送給用戶端。對於所有其他伺服器 API,kernel.terminate
的監聽器仍然會被執行,但回應要等到它們全部完成後才會傳送給用戶端。
注意
使用 kernel.terminate
事件是選用的,並且僅應在您的 Kernel 實作 TerminableInterface 時才應呼叫。
9) 處理例外:kernel.exception
事件
典型用途:處理某些類型的例外,並建立適當的 Response
以針對例外回傳
如果在 HttpKernel::handle()
內部任何時間點拋出例外,則會分派另一個事件 - kernel.exception
。在內部,handle()
方法的主體被包裝在 try-catch 區塊中。當拋出任何例外時,會分派 kernel.exception
事件,以便您的系統可以以某種方式回應例外。
此事件的每個監聽器都會傳遞一個 ExceptionEvent 物件,您可以使用它透過 getThrowable() 方法存取原始例外。此事件上的典型監聽器將檢查特定類型的例外,並建立適當的錯誤 Response
。
例如,為了產生 404 頁面,您可能會拋出特殊類型的例外,然後在此事件上新增一個監聽器,該監聽器會尋找此例外,並建立及回傳 404 Response
。事實上,HttpKernel 元件帶有一個 ErrorListener,如果您選擇使用它,預設情況下會執行此操作以及更多操作(請參閱下面的側邊欄以取得更多詳細資訊)。
ExceptionEvent 公開了 isKernelTerminating() 方法,您可以使用它來判斷 Kernel 在拋出例外時是否正在終止。
7.1
isKernelTerminating() 方法是在 Symfony 7.1 中引入的。
注意
當為 kernel.exception
事件設定回應時,傳播會停止。這表示優先順序較低的監聽器將不會被執行。
建立事件監聽器
如您所見,您可以建立事件監聽器並將其附加到 HttpKernel::handle()
週期期間分派的任何事件。通常,監聽器是一個帶有要執行的方法的 PHP 類別,但它可以是任何東西。有關建立和附加事件監聽器的更多資訊,請參閱 EventDispatcher 元件。
每個「Kernel」事件的名稱都定義為 KernelEvents 類別上的常數。此外,每個事件監聽器都會傳遞一個單一引數,它是 KernelEvent 的一些子類別。此物件包含有關系統目前狀態的資訊,並且每個事件都有自己的事件物件
名稱 | KernelEvents 常數 |
傳遞給監聽器的引數 |
---|---|---|
kernel.request | KernelEvents::REQUEST |
RequestEvent |
kernel.controller | KernelEvents::CONTROLLER |
ControllerEvent |
kernel.controller_arguments | KernelEvents::CONTROLLER_ARGUMENTS |
ControllerArgumentsEvent |
kernel.view | KernelEvents::VIEW |
ViewEvent |
kernel.response | KernelEvents::RESPONSE |
ResponseEvent |
kernel.finish_request | KernelEvents::FINISH_REQUEST |
FinishRequestEvent |
kernel.terminate | KernelEvents::TERMINATE |
TerminateEvent |
kernel.exception | KernelEvents::EXCEPTION |
ExceptionEvent |
完整的運作範例
當使用 HttpKernel 元件時,您可以自由地將任何監聽器附加到核心事件,使用任何實作 ControllerResolverInterface 的控制器解析器,以及使用任何實作 ArgumentResolverInterface 的引數解析器。但是,HttpKernel 元件帶有一些內建的監聽器以及其他可用於建立工作範例的所有東西
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
use Symfony\Component\EventDispatcher\EventDispatcher;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Controller\ArgumentResolver;
use Symfony\Component\HttpKernel\Controller\ControllerResolver;
use Symfony\Component\HttpKernel\EventListener\RouterListener;
use Symfony\Component\HttpKernel\HttpKernel;
use Symfony\Component\Routing\Matcher\UrlMatcher;
use Symfony\Component\Routing\RequestContext;
use Symfony\Component\Routing\Route;
use Symfony\Component\Routing\RouteCollection;
$routes = new RouteCollection();
$routes->add('hello', new Route('/hello/{name}', [
'_controller' => function (Request $request): Response {
return new Response(
sprintf("Hello %s", $request->get('name'))
);
}]
));
$request = Request::createFromGlobals();
$matcher = new UrlMatcher($routes, new RequestContext());
$dispatcher = new EventDispatcher();
$dispatcher->addSubscriber(new RouterListener($matcher, new RequestStack()));
$controllerResolver = new ControllerResolver();
$argumentResolver = new ArgumentResolver();
$kernel = new HttpKernel($dispatcher, $controllerResolver, new RequestStack(), $argumentResolver);
$response = $kernel->handle($request);
$response->send();
$kernel->terminate($request, $response);
子請求
除了傳送到 HttpKernel::handle()
的「主要」請求之外,您還可以傳送所謂的「子請求」。子請求的外觀和行為都像任何其他請求,但通常僅用於渲染頁面的一小部分,而不是整個頁面。您最常從控制器(或可能從控制器正在渲染的模板內部)發出子請求。
若要執行子請求,請使用 HttpKernel::handle()
,但將第二個引數變更如下
1 2 3 4 5 6 7 8 9 10 11 12
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\HttpKernelInterface;
// ...
// create some other request manually as needed
$request = new Request();
// for example, possibly set its _controller manually
$request->attributes->set('_controller', '...');
$response = $kernel->handle($request, HttpKernelInterface::SUB_REQUEST);
// do something with this response
這會建立另一個完整的請求-回應週期,其中這個新的 Request
會轉換為 Response
。內部的唯一區別在於,某些監聽器(例如安全性)可能僅對主要請求起作用。每個監聽器都會傳遞 KernelEvent 的某些子類別,其 isMainRequest() 方法可用於檢查目前的請求是「主要」請求還是「子」請求。
例如,只需要對主要請求起作用的監聽器可能看起來像這樣
1 2 3 4 5 6 7 8 9 10 11
use Symfony\Component\HttpKernel\Event\RequestEvent;
// ...
public function onKernelRequest(RequestEvent $event): void
{
if (!$event->isMainRequest()) {
return;
}
// ...
}
注意
_format
請求屬性的預設值是 html
。如果您的子請求回傳不同的格式(例如 json
),您可以透過在請求上明確定義 _format
屬性來設定它
1
$request->attributes->set('_format', 'json');
定位資源
HttpKernel 元件負責 Symfony 應用程式中使用的套件機制。套件的關鍵功能之一是,您可以使用邏輯路徑而不是實體路徑來參照它們的任何資源(組態檔、模板、控制器、翻譯檔等)。
即使您不知道套件將安裝在檔案系統中的哪個位置,也可以匯入資源。例如,儲存在名為 FooBundle 的套件的 Resources/config/
目錄中的 services.xml
檔案可以參照為 @FooBundle/Resources/config/services.xml
而不是 __DIR__/Resources/config/services.xml
。
這要歸功於 Kernel 提供的 locateResource() 方法,該方法將邏輯路徑轉換為實體路徑
1
$path = $kernel->locateResource('@FooBundle/Resources/config/services.xml');