首頁 >後端開發 >php教程 >前控制器圖案的簡介,第2部分

前控制器圖案的簡介,第2部分

Lisa Kudrow
Lisa Kudrow原創
2025-02-26 09:55:45818瀏覽

An Introduction to the Front Controller Pattern, Part 2

核心要點

  • 前端控制器充當應用程序的集中式代理,將命令分派給預定義的處理程序,例如頁面控制器或 REST 資源。
  • 前端控制器可以保持緊湊的結構,路由和分派傳入請求,也可以擴展為功能齊全的 RESTful 控制器,解析 HTTP 動詞,適應預/後分派鉤子等。
  • 本文演示瞭如何部署一個小型但可擴展的 HTTP 框架,該框架能夠與前端控制器、獨立路由器和調度程序一起工作,同時獨立處理請求/響應週期。
  • 作者還介紹了從頭開始構建前端控制器的過程,包括定義類來模擬典型 HTTP 請求/響應週期的數據和行為,構建路由模塊以及設置調度程序。
  • 前端控制器模式具有集中控制、減少代碼重複以及改進模塊化和關注點分離等優點,但它可能不適用於所有 Web 應用程序,如果實現不當,可能會導致單點故障。

前端控制器就像應用程序中的集中式代理,其主要關注領域是靜態或動態地將命令分派給預定義的處理程序,例如頁面控制器、REST 資源或任何其他想到的東西。構建至少一個簡單的前端控制器是理解其細節的非常有益的經驗,為了從務實的角度推廣這一想法,我在介紹性文章中介紹了一個人為的前端控制器的實現,該控制器將路由和分派請求所需的所有邏輯打包在一個類的邊界內。前端控制器最好的方面之一是您可以將它們保持為緊湊的結構,只路由和分派傳入請求,或者您可以展現您的創造力,並實現一個功能齊全的RESTful 控制器,能夠解析HTTP動詞,適應預/後分派鉤子等等,所有這些都在統一的API 後面。但是,雖然這種方法很有吸引力,但它違反了單一職責原則 (SRP) 並違背了 OOP 本身主動將不同任務委託給多個細粒度對象的本質。那麼,這是否意味著我只是另一個膽敢違反 SRP 戒律的有罪靈魂呢?從某種意義上說,是的。因此,我想通過向您展示如何輕鬆部署一個小型但可擴展的 HTTP 框架來消除我的罪惡,該框架能夠與獨立路由器和調度程序一起使用前端控制器。此外,整個請求/響應週期將由幾個可重用的類獨立處理,這些類自然可以隨意調整。鑑於有大量功能齊全的組件打包的 HTTP 框架可用,從頭開始實現一個通過幾個模塊化類路由和分派請求的前端控制器似乎很荒謬,即使這些類保留了 SRP 的本質。為了避免因重新發明輪子而受到評判,我的自定義實現的一些部分將受到 Lars Strojny 編寫的巧妙的 EPHPMVC 庫的啟發。

剖析請求/路由/分派/響應週期

我們應該首先解決的任務是定義幾個負責模擬典型 HTTP 請求/響應週期的數據和行為的類。這是第一個類,以及它實現的接口:

<code>class Request {

  public function __construct($uri, $params) { 
    $this->uri = $uri;
    $this->params = $params;
  }

  public function getUri() {
    return $this->uri;
  }

  public function setParam($key, $value) {
    $this->params[$key] = $value;
    return $this;
  }

  public function getParam($key) {
    if (!isset($this->params[$key])) {
      throw new \InvalidArgumentException("The request parameter with key '$key' is invalid."); 
    }
    return $this->params[$key];
  }

  public function getParams() {
    return $this->params;
  }
}</code>

Request 類封裝了傳入的 URI 和參數數組,並模擬了一個極其簡單的 HTTP 請求。為簡潔起見,諸如與相關請求關聯的方法集之類的附加數據成員已故意排除在外。如果您想將它們添加到類中,請繼續操作。擁有一個獨立存在的精簡 HTTP 請求包裝器很好,但如果沒有與模擬典型 HTTP 響應的數據和行為的對應部分耦合,最終將毫無用處。讓我們修復並構建這個補充組件:

<code>class Response {
  public function __construct($version) {
    $this->version = $version;
  }

  public function getVersion() {
    return $this->version;
  }

  public function addHeader($header) {
    $this->headers[] = $header;
    return $this;
  }

  public function addHeaders(array $headers) {
    foreach ($headers as $header) {
      $this->addHeader($header);
    }
    return $this;
  }

  public function getHeaders() {
    return $this->headers;
  }

  public function send() {
    if (!headers_sent()) {
      foreach($this->headers as $header) {
        header("$this->version $header", true);
      }
    } 
  }
}</code>

Response 類無疑比其夥伴 Request 更活躍。它充當一個基本容器,允許您隨意堆疊 HTTP 標頭,並且能夠將它們發送到客戶端。由於這些類獨立地執行其操作,因此是時候著手構建前端控制器的下一部分了。在典型的實現中,路由/分派過程大多數情況下封裝在相同的方法中,坦白地說,這根本不算太壞。但是,在這種情況下,最好將這些過程分解並將其委託給不同的類。這樣,事情在責任平等方面會更加平衡。以下是使路由模塊運行的類批次:

<code>class Route {

  public function __construct($path, $controllerClass) {
    $this->path = $path;
    $this->controllerClass = $controllerClass;
  }

  public function match(RequestInterface $request) {
    return $this->path === $request->getUri();
  }

  public function createController() {
   return new $this->controllerClass;
  }
}


class Router {
  public function __construct($routes) {
    $this->addRoutes($routes);
  }

  public function addRoute(RouteInterface $route) {
    $this->routes[] = $route;
    return $this;
  }

  public function addRoutes(array $routes) {
    foreach ($routes as $route) {
      $this->addRoute($route);
    }
    return $this;
  }

  public function getRoutes() {
    return $this->routes;
  }

  public function route(RequestInterface $request, ResponseInterface $response) {
    foreach ($this->routes as $route) {
      if ($route->match($request)) {
        return $route;
      }
    }
    $response->addHeader("404 Page Not Found")->send();
    throw new \OutOfRangeException("No route matched the given URI.");
  }
}</code>

正如人們可能預期的那樣,在實現功能性路由機制時,有很多選擇。至少在我看來,上述方法既實用又直接。它定義了一個獨立的 Route 類,該類將路徑綁定到給定的操作控制器,以及一個簡單的路由器,其職責僅限於檢查存儲的路由是否與特定請求對象關聯的 URI 匹配。為了最終解決問題,我們需要設置一個快速調度程序,可以與之前的類並排使用。下面的類就是這樣做的:

<code>class Dispatcher {

  public function dispatch($route, $request, $response)
    $controller = $route->createController();
    $controller->execute($request, $response);
  }
}</code>

掃描 Dispatcher,您會注意到兩件事。首先,它不攜帶任何狀態。其次,它隱式地假設每個操作控制器都將在 execute() 方法的表面下運行。如果您願意,這可以被重構為稍微更靈活的模式(我首先想到的是調整 Route 類的實現),但為了簡單起見,我將保持調度程序不變。到目前為止,您可能想知道如何在何處放置能夠將所有先前類組合在一起的前端控制器。別著急,接下來就是!

(由於篇幅限制,後續內容將被截斷。請提供剩餘部分,我將繼續完成偽原創。)

以上是前控制器圖案的簡介,第2部分的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn