摘要:路由路由的功能就是分發請求到不同的控制器,基于的原理就是正則匹配。
路由
路由的功能就是分發請求到不同的控制器,基于的原理就是正則匹配。接下來呢,我們實現一個簡單的路由器,實現的能力是
對于靜態的路由(沒占位符的),正確調用callback
對于有占位符的路由,正確調用callback時傳入占位符參數,譬如對于路由:/user/{id},當請求為/user/23時,傳入參數$args結構為
[ "id" => "23" ]大致思路
我們需要把每個路由的信息管理起來:http方法($method),路由字符串($route),回調($callback),因此需要一個addRoute方法,另外提供短方法get,post(就是把$method寫好)
對于/user/{id}這樣的有占位符的路由字符串,把占位符要提取出來,然后占位符部分變成正則字符串
代碼講解 路由分類對于注冊的路由,需要分成兩類(下文提到的$uri是指$_SERVER["REQUEST_URI"]去掉查詢字符串的值)
靜態路由(就是沒有占位符的路由,例如/articles)
帶參數路由(有占位符的路由,例如/user/{uid})
其實這是很明顯的,因為靜態的路由的話,我們只需要和$uri直接比較相等與否就行了,而對于帶參數路由,譬如/user/{uid},我們需要在注冊的時候,提取占位符名,把{**}這一部分替換為([a-zA-Z0-9_]+)這樣的正則字符串,使用()是因為要做分組捕獲,把占位符對應的值要取出來。
Dispatcher.php中有兩個數組
$staticRoutes
$methodToRegexToRoutesMap
分別對應靜態路由和帶參數路由,另外要注意,這兩個數組是二維數組,第一維存儲http method,第二維用來存儲正則字符串(靜態路由自身就是這個值,而帶參數路由是把占位符替換后的值),最后的value是一個Route對象
Route類這個類很好理解,用來存儲注冊路由的一些信息
$httpMethod:HTTP方法,有GET,POST,PUT,PATCH,HEAD
$regex:路由的正則表達式,帶參數路由是占位符替換后的值,靜態路由自身就是這個值
$variables:路由占位符參數集合,靜態路由就是空數組
$handler:路由要回調的對象
當然,這個類還可以多存儲一點信息,譬如帶參數路由最原始的字符串(/user/{uid}),這里簡單做了
分發流程根據http method取數據,因為第一維都是http method
一個個匹配靜態路由
對于帶參數路由,把所有的正則表達式合起來,形成一個大的正則字符串,而不是一個個匹配(這樣效率低)
第一步很簡單,主要說明第二步
對于三個獨立的正則字符串(定界符是~):
~^/user/([^/]+)/(d+)$~ ~^/user/(d+)$~ ~^/user/([^/]+)$~
我們可以合起來,得到
~^(?: /user/([^/]+)/(d+) | /user/(d+) | /user/([^/]+) )$~x
?:是非捕獲型分組
這個轉化很簡單,我們怎么知道那個正則被匹配了呢??
舉個例子:
preg_match($regex, "/user/nikic", $matches); => [ "/user/nikic", # 完全匹配 "", "", # 第一個(空) "", # 第二個(空) "nikic", # 第三個(被使用) ]
可以看到,第一個非空的位置就可以推斷出哪個路由被匹配了(第一個完全匹配要剔除),我們需要一個數組要映射它
[ 1 => ["handler0", ["name", "id"]], 3 => ["handler1", ["id"]], 4 => ["handler2", ["name"]], ]
1是因為排除第一個匹配
3是因為第一個路由有兩個占位符
4是因為第二個路由有一個占位符
上面的數組,我們可以注冊的methodToRegexToRoutesMap這個量形成的,我是這么寫的
$regexes = array_keys($this->methodToRegexToRoutesMap[$httpMethod]); foreach ($regexes as $regex) { $routeLookup[$index] = [ $this->methodToRegexToRoutesMap[$httpMethod][$regex]->handler, $this->methodToRegexToRoutesMap[$httpMethod][$regex]->variables, ]; $index += count($this->methodToRegexToRoutesMap[$httpMethod][$regex]->variables); }最后
調用回調函數,返回一個數組,第一個值用來判斷最終有沒有找到
實現 Route.php類httpMethod = $httpMethod; $this->handler = $handler; $this->regex = $regex; $this->variables = $variables; } /** * Tests whether this route matches the given string. * * @param string $str * * @return bool */ public function matches($str) { $regex = "~^" . $this->regex . "$~"; return (bool) preg_match($regex, $str); } }Dispatcher.php
1) { preg_match_all("~{([a-zA-Z0-9_]+?)}~", $route, $matchesVariables); return [ preg_replace("~{[a-zA-Z0-9_]+?}~", "([a-zA-Z0-9_]+)", $route), $matchesVariables[1], ]; } else { return [ $route, [], ]; } } throw new LogicException("register route failed, pattern is illegal"); } /** * 注冊路由 * @param $httpMethod string | string[] * @param $route * @param $handler */ public function addRoute($httpMethod, $route, $handler) { $routeData = $this->parse($route); foreach ((array) $httpMethod as $method) { if ($this->isStaticRoute($routeData)) { $this->addStaticRoute($httpMethod, $routeData, $handler); } else { $this->addVariableRoute($httpMethod, $routeData, $handler); } } } private function isStaticRoute($routeData) { return count($routeData[1]) === 0; } private function addStaticRoute($httpMethod, $routeData, $handler) { $routeStr = $routeData[0]; if (isset($this->staticRoutes[$httpMethod][$routeStr])) { throw new LogicException(sprintf( "Cannot register two routes matching "%s" for method "%s"", $routeStr, $httpMethod )); } if (isset($this->methodToRegexToRoutesMap[$httpMethod])) { foreach ($this->methodToRegexToRoutesMap[$httpMethod] as $route) { if ($route->matches($routeStr)) { throw new LogicException(sprintf( "Static route "%s" is shadowed by previously defined variable route "%s" for method "%s"", $routeStr, $route->regex, $httpMethod )); } } } $this->staticRoutes[$httpMethod][$routeStr] = $handler; } private function addVariableRoute($httpMethod, $routeData, $handler) { list($regex, $variables) = $routeData; if (isset($this->methodToRegexToRoutesMap[$httpMethod][$regex])) { throw new LogicException(sprintf( "Cannot register two routes matching "%s" for method "%s"", $regex, $httpMethod )); } $this->methodToRegexToRoutesMap[$httpMethod][$regex] = new Route( $httpMethod, $handler, $regex, $variables ); } public function get($route, $handler) { $this->addRoute("GET", $route, $handler); } public function post($route, $handler) { $this->addRoute("POST", $route, $handler); } public function put($route, $handler) { $this->addRoute("PUT", $route, $handler); } public function delete($route, $handler) { $this->addRoute("DELETE", $route, $handler); } public function patch($route, $handler) { $this->addRoute("PATCH", $route, $handler); } public function head($route, $handler) { $this->addRoute("HEAD", $route, $handler); } /** * 分發 * @param $httpMethod * @param $uri */ public function dispatch($httpMethod, $uri) { $staticRoutes = array_keys($this->staticRoutes[$httpMethod]); foreach ($staticRoutes as $staticRoute) { if($staticRoute === $uri) { return [self::FOUND, $this->staticRoutes[$httpMethod][$staticRoute], []]; } } $routeLookup = []; $index = 1; $regexes = array_keys($this->methodToRegexToRoutesMap[$httpMethod]); foreach ($regexes as $regex) { $routeLookup[$index] = [ $this->methodToRegexToRoutesMap[$httpMethod][$regex]->handler, $this->methodToRegexToRoutesMap[$httpMethod][$regex]->variables, ]; $index += count($this->methodToRegexToRoutesMap[$httpMethod][$regex]->variables); } $regexCombined = "~^(?:" . implode("|", $regexes) . ")$~"; if(!preg_match($regexCombined, $uri, $matches)) { return [self::NOT_FOUND]; } for ($i = 1; "" === $matches[$i]; ++$i); list($handler, $varNames) = $routeLookup[$i]; $vars = []; foreach ($varNames as $varName) { $vars[$varName] = $matches[$i++]; } return [self::FOUND, $handler, $vars]; } }配置 nginx.conf重寫到index.php
location / { try_files $uri $uri/ /index.php$is_args$args; # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000 # location ~ .php$ { fastcgi_pass 127.0.0.1:9000; fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; include fastcgi_params; } }composer.json自動載入
{ "name": "salmander/route", "require": {}, "autoload": { "psr-4": { "SalamanderRoute": "SalamanderRoute/" } } }最終使用 index.php
get("/", function () { echo "hello world"; }); $dispatcher->get("/user/{id}", function ($args) { echo "user {$args["id"]} visit"; }); // Fetch method and URI from somewhere $httpMethod = $_SERVER["REQUEST_METHOD"]; $uri = $_SERVER["REQUEST_URI"]; // 去掉查詢字符串 if (false !== $pos = strpos($uri, "?")) { $uri = substr($uri, 0, $pos); } $routeInfo = $dispatcher->dispatch($httpMethod, $uri); switch ($routeInfo[0]) { case Dispatcher::NOT_FOUND: echo "404 not found"; break; case Dispatcher::FOUND: $handler = $routeInfo[1]; $vars = $routeInfo[2]; $handler($vars); break; }
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/26079.html
改進 緊接上一篇文章Just for fun——PHP框架之簡單的路由器(1)。代碼下載 效率不高原因 對于以下合并的正則 ~^(?: /user/([^/]+)/(d+) | /user/(d+) | /user/([^/]+) )$~x 最終匹配的是分組中的某一個,我們需要的子匹配也是那個分組中的,然而從結果看 preg_match($regex, /user/niki...
摘要:使開發人員可以編寫高性能的異步并發,服務。使用作為網絡通信框架,可以使企業研發團隊的效率大大提升,更加專注于開發創新產品。總之,這個庫讓可以常駐內存,并提供了,等功能。 swoole 使 PHP 開發人員可以編寫高性能的異步并發 TCP、UDP、Unix Socket、HTTP,WebSocket 服務。Swoole 可以廣泛應用于互聯網、移動通信、企業軟件、云計算、網絡游戲、物聯網(...
摘要:使開發人員可以編寫高性能的異步并發,服務。使用作為網絡通信框架,可以使企業研發團隊的效率大大提升,更加專注于開發創新產品。總之,這個庫讓可以常駐內存,并提供了,等功能。 swoole 使 PHP 開發人員可以編寫高性能的異步并發 TCP、UDP、Unix Socket、HTTP,WebSocket 服務。Swoole 可以廣泛應用于互聯網、移動通信、企業軟件、云計算、網絡游戲、物聯網(...
摘要:原理使用模板引擎的好處是數據和視圖分離。對于循環語句怎么辦呢這個的話,請看流程控制的替代語法 原理 使用模板引擎的好處是數據和視圖分離。一個簡單的PHP模板引擎原理是 extract數組($data),使key對應的變量可以在此作用域起效 打開輸出控制緩沖(ob_start) include模板文件,include遇到html的內容會輸出,但是因為打開了緩沖,內容輸出到了緩沖中 ob...
摘要:的話,是一個遵循規范微型的框架,作者這樣說大致意思的核心工作分發了請求,然后調用回調函數,返回一個對象。執行的方法時,我們從中取出的依賴,這時候,注冊的回調函數被調用,返回實例。 Slim Slim的話,是一個遵循PSR (PSR-7)規范微型的框架,作者這樣說: Slim is a PHP micro framework that helps you quickly write si...
閱讀 2461·2023-04-26 02:18
閱讀 1262·2021-10-14 09:43
閱讀 3822·2021-09-26 10:00
閱讀 6945·2021-09-22 15:28
閱讀 2535·2019-08-30 15:54
閱讀 2600·2019-08-30 15:52
閱讀 474·2019-08-29 11:30
閱讀 3465·2019-08-29 11:05