這個庫提供了基於正規表示式的快速路由實現。這篇文章解釋了 FastRoute 是如何工作的和它為什麼很快。
安裝
通過 composer 安裝
composer require nikic/fast-route
要求 PHP 5.4 及更高的版本
使用
這是一個基本的使用範例
<?php require '/path/to/vendor/autoload.php'; $dispatcher = FastRoutesimpleDispatcher(function(FastRouteRouteCollector $r) { $r->addRoute('GET', '/users', 'get_all_users_handler'); // {id} 必須是一個數位 (d+) $r->addRoute('GET', '/user/{id:d+}', 'get_user_handler'); // /{title} 字尾是可選的 $r->addRoute('GET', '/articles/{id:d+}[/{title}]', 'get_article_handler'); }); // 獲取請求的方法和 URI $httpMethod = $_SERVER['REQUEST_METHOD']; $uri = $_SERVER['REQUEST_URI']; // 去除查詢字串( ? 後面的內容) 和 解碼 URI if (false !== $pos = strpos($uri, '?')) { $uri = substr($uri, 0, $pos); } $uri = rawurldecode($uri); $routeInfo = $dispatcher->dispatch($httpMethod, $uri); switch ($routeInfo[0]) { case FastRouteDispatcher::NOT_FOUND: // ... 404 Not Found 沒找到對應的方法 break; case FastRouteDispatcher::METHOD_NOT_ALLOWED: $allowedMethods = $routeInfo[1]; // ... 405 Method Not Allowed 方法不允許 break; case FastRouteDispatcher::FOUND: // 找到對應的方法 $handler = $routeInfo[1]; // 獲得處理常式 $vars = $routeInfo[2]; // 獲取請求引數 // ... call $handler with $vars // 呼叫處理常式 break; }
定義路由
通過呼叫 FastRoutesimpleDispatcher()
函數來定義路由,該函數接受一個以 FastRouteRouteCollector
範例為引數的閉包作為引數。通過在 collector
範例裡面呼叫 addRoute()
增加路由。
$r->addRoute($method, $routePattern, $handler);
$method
是大寫的 HTTP 方法,能夠被某個路由匹配,可以使用陣列指定多個有效的 $method
。
// 這裡兩行呼叫 $r->addRoute('GET', '/test', 'handler'); $r->addRoute('POST', '/test', 'handler'); // 等同於這一行呼叫 $r->addRoute(['GET', 'POST'], '/test', 'handler');
預設情況下 $routePattern
使用一種語法,比如 {foo}
是指定名稱為 foo 的預留位置,可以匹配正規表示式 [^/]+.
。要調整預留位置匹配的模式,可以通過編寫 {bar:[0-9] +}
來指定自定義模式。一些例子
// 匹配 /user/42,不匹配 /user/xyx $r->addRoute('GET', '/user/{id:d+}', 'handler'); // 匹配 /user/foobar,不匹配 /user/foo/bar $r->addRoute('GET', '/user/{name}', 'handler'); // 匹配 /user/foobar,也匹配 /user/foo/bar $r->addRoute('GET', '/user/{name:.+}', 'handler');
路由預留位置的自定義模式不能使用捕獲組,例如 {lang:(en|de)}
不是有效的預留位置,因為 () 是一個捕獲組,可以使用 {lang:en|de}
或者 {lang:(?:en|de)}
代替。
另外,在路由 [...] 中定義的部分是可選匹配的,所以 /foo[bar]
將匹配 /foo 和 /foobar 。路由可選部分只支援在定義的末尾,而不能在定義的中間。
// 這個路由有,[/{name}] 可選擇匹配部分 $r->addRoute('GET', '/user/{id:d+}[/{name}]', 'handler'); // 等同於這兩個路由 $r->addRoute('GET', '/user/{id:d+}', 'handler'); $r->addRoute('GET', '/user/{id:d+}/{name}', 'handler'); // 多層巢狀可選路由,也是支援的 $r->addRoute('GET', '/user[/{id:d+}[/{name}]]', 'handler'); // 這個路由定義無效,因為可選部分只能在定義的末尾 $r->addRoute('GET', '/user[/{id:d+}]/{name}', 'handler');
$handler
引數不一定必須是回撥函數,它也可以是控制器類名或任何其他型別的資料。FastRoute 只告訴你哪個 handler 對應 URI,如何解釋它取決於你。
請求方法的書寫快捷方式
對於 GET
、POST
、PUT
、PATCH
、DELETE
和 HEAD
請求方法,可使用快捷方式。
$r->get('/get-route', 'get_handler'); $r->post('/post-route', 'post_handler'); // 等同於 $r->addRoute('GET', '/get-route', 'get_handler'); $r->addRoute('POST', '/post-route', 'post_handler');
路由組
你可以在一個組內定義路由,同一組內的路由有相同的字首。
$r->addGroup('/admin', function (RouteCollector $r) { $r->addRoute('GET', '/do-something', 'handler'); $r->addRoute('GET', '/do-another-thing', 'handler'); $r->addRoute('GET', '/do-something-else', 'handler'); }); // 等同於 $r->addRoute('GET', '/admin/do-something', 'handler'); $r->addRoute('GET', '/admin/do-another-thing', 'handler'); $r->addRoute('GET', '/admin/do-something-else', 'handler');
可以定義多層巢狀組結構。
快取
使用 simpleDispatcher
定義路由的回撥函數可以無縫快取。通過使用 cachedDispatcher
而不是 simpleDispatcher
,可以快取生成的路由資料並從快取的資訊構建排程。
<?php $dispatcher = FastRoutecachedDispatcher(function(FastRouteRouteCollector $r) { $r->addRoute('GET', '/user/{name}/{id:[0-9]+}', 'handler0'); $r->addRoute('GET', '/user/{id:[0-9]+}', 'handler1'); $r->addRoute('GET', '/user/{name}', 'handler2'); }, [ 'cacheFile' => __DIR__ . '/route.cache', /* required 快取檔案路徑,必須設定 */ 'cacheDisabled' => IS_DEBUG_ENABLED, /* optional, enabled by default 是否快取,可選引數,預設情況下開啟 */ ]);
該函數的第二個引數是一個選項陣列,可用於指定快取檔案路徑等等。
排程 URI
通過呼叫 dispatch()
排程 URI。這個方法接受 HTTP 方法 和一個 URI 作為引數。獲得這兩個資訊是你自己的工作,這個庫並不系結到 PHP web SAPIs 。
dispatch()
返回一個陣列,第一個元素是一個狀態碼,狀態碼是 Dispatcher::NOT_FOUND
、Dispatcher::METHOD_NOT_ALLOWED
、Dispatcher::FOUND
其中之一。對於 Dispatcher::METHOD_NOT_ALLOWED
狀態,第二個陣列元素包含允許提供的 URI 的 HTTP 方法列表。
[FastRouteDispatcher::METHOD_NOT_ALLOWED, ['GET', 'POST']]
對於 Dispatcher::FOUND
狀態,第二個陣列元素是 $handler
,第三個陣列元素是是一個包含所有預留位置的陣列
/* Routing against GET /user/nikic/42 */ [FastRouteDispatcher::FOUND, 'handler0', ['name' => 'nikic', 'id' => '42']]
重寫路由解析器和排程器
這個庫使用三個元件,一個路由解析器,一個資料生成器,一個排程器。這個三個元件實現以下介面
<?php namespace FastRoute; interface RouteParser { public function parse($route); } interface DataGenerator { public function addRoute($httpMethod, $routeData, $handler); public function getData(); } interface Dispatcher { const NOT_FOUND = 0, FOUND = 1, METHOD_NOT_ALLOWED = 2; public function dispatch($httpMethod, $uri); }
路由解析器獲取路由模式字串並將其轉換為路由資訊陣列,其中每個路線資訊又是它的部分陣列。
/* The route /user/{id:d+}[/{name}] converts to the following array: */ [ [ '/user/', ['id', 'd+'], ], [ '/user/', ['id', 'd+'], '/', ['name', '[^/]+'], ], ]
然後可以將該陣列傳遞給資料生成器的 addRoute()
方法,在新增了所有路由之後,呼叫生成器的 getData()
,它將返回撥度器所需的所有路由資料。
排程程式通過建構函式接受路由資料,並提供 dispatch()
方法。
路由解析器可以被單獨覆蓋,然而資料生成器和排程器應該總是一起修改,因為前者的輸出與後者的輸入緊密耦合。
當使用 simpleDispatcher / cachedDispatcher
時,可以通過傳入額外的引數,進行覆蓋
<?php $dispatcher = FastRoutesimpleDispatcher(function(FastRouteRouteCollector $r) { /* ... */ }, [ 'routeParser' => 'FastRouteRouteParserStd', 'dataGenerator' => 'FastRouteDataGeneratorGroupCountBased', 'dispatcher' => 'FastRouteDispatcherGroupCountBased', ]);
上面給出了預設的設定,通過把 GroupCountBased
替換成 GroupPosBased
可以使用完全不同的排程策略
關於HEAD請求的說明
HTTP 規範要求伺服器 同時支援 GET
和 HEAD
方法
GET
和HEAD
方法必須得到所有通用伺服器的支援
為避免強制使用者為每個資源手動註冊 HEAD
路由,將使用一個匹配的 GET
路由響應請求。PHP web SAPI 透明地從 HEAD
響應中移除實體主體,所以這種行為對絕大多數使用者沒有影響。
但是,在 Web SAPI 環境外部使用 FastRoute ,絕不能傳送響應 HEAD
請求而生成的實體主體,如果你是非 SAPI 使用者,這是你的責任;在這種情況下,FastRoute 無許可權制你破壞 HTTP 。
最後,請注意,應用程式可以始終為給定資源指定其自己的 HEAD
方法路由以完全繞過此行為。