摘要:路由路由的功能就是分發(fā)請求到不同的控制器,基于的原理就是正則匹配。
路由
路由的功能就是分發(fā)請求到不同的控制器,基于的原理就是正則匹配。接下來呢,我們實現(xiàn)一個簡單的路由器,實現(xiàn)的能力是
對于靜態(tài)的路由(沒占位符的),正確調(diào)用callback
對于有占位符的路由,正確調(diào)用callback時傳入占位符參數(shù),譬如對于路由:/user/{id},當(dāng)請求為/user/23時,傳入?yún)?shù)$args結(jié)構(gòu)為
[ "id" => "23" ]大致思路
我們需要把每個路由的信息管理起來:http方法($method),路由字符串($route),回調(diào)($callback),因此需要一個addRoute方法,另外提供短方法get,post(就是把$method寫好)
對于/user/{id}這樣的有占位符的路由字符串,把占位符要提取出來,然后占位符部分變成正則字符串
代碼講解 路由分類對于注冊的路由,需要分成兩類(下文提到的$uri是指$_SERVER["REQUEST_URI"]去掉查詢字符串的值)
靜態(tài)路由(就是沒有占位符的路由,例如/articles)
帶參數(shù)路由(有占位符的路由,例如/user/{uid})
其實這是很明顯的,因為靜態(tài)的路由的話,我們只需要和$uri直接比較相等與否就行了,而對于帶參數(shù)路由,譬如/user/{uid},我們需要在注冊的時候,提取占位符名,把{**}這一部分替換為([a-zA-Z0-9_]+)這樣的正則字符串,使用()是因為要做分組捕獲,把占位符對應(yīng)的值要取出來。
Dispatcher.php中有兩個數(shù)組
$staticRoutes
$methodToRegexToRoutesMap
分別對應(yīng)靜態(tài)路由和帶參數(shù)路由,另外要注意,這兩個數(shù)組是二維數(shù)組,第一維存儲http method,第二維用來存儲正則字符串(靜態(tài)路由自身就是這個值,而帶參數(shù)路由是把占位符替換后的值),最后的value是一個Route對象
Route類這個類很好理解,用來存儲注冊路由的一些信息
$httpMethod:HTTP方法,有GET,POST,PUT,PATCH,HEAD
$regex:路由的正則表達式,帶參數(shù)路由是占位符替換后的值,靜態(tài)路由自身就是這個值
$variables:路由占位符參數(shù)集合,靜態(tài)路由就是空數(shù)組
$handler:路由要回調(diào)的對象
當(dāng)然,這個類還可以多存儲一點信息,譬如帶參數(shù)路由最原始的字符串(/user/{uid}),這里簡單做了
分發(fā)流程根據(jù)http method取數(shù)據(jù),因為第一維都是http method
一個個匹配靜態(tài)路由
對于帶參數(shù)路由,把所有的正則表達式合起來,形成一個大的正則字符串,而不是一個個匹配(這樣效率低)
第一步很簡單,主要說明第二步
對于三個獨立的正則字符串(定界符是~):
~^/user/([^/]+)/(d+)$~ ~^/user/(d+)$~ ~^/user/([^/]+)$~
我們可以合起來,得到
~^(?: /user/([^/]+)/(d+) | /user/(d+) | /user/([^/]+) )$~x
?:是非捕獲型分組
這個轉(zhuǎn)化很簡單,我們怎么知道那個正則被匹配了呢??
舉個例子:
preg_match($regex, "/user/nikic", $matches); => [ "/user/nikic", # 完全匹配 "", "", # 第一個(空) "", # 第二個(空) "nikic", # 第三個(被使用) ]
可以看到,第一個非空的位置就可以推斷出哪個路由被匹配了(第一個完全匹配要剔除),我們需要一個數(shù)組要映射它
[ 1 => ["handler0", ["name", "id"]], 3 => ["handler1", ["id"]], 4 => ["handler2", ["name"]], ]
1是因為排除第一個匹配
3是因為第一個路由有兩個占位符
4是因為第二個路由有一個占位符
上面的數(shù)組,我們可以注冊的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); }最后
調(diào)用回調(diào)函數(shù),返回一個數(shù)組,第一個值用來判斷最終有沒有找到
實現(xiàn) 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); } /** * 分發(fā) * @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; }
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/26079.html
改進 緊接上一篇文章Just for fun——PHP框架之簡單的路由器(1)。代碼下載 效率不高原因 對于以下合并的正則 ~^(?: /user/([^/]+)/(d+) | /user/(d+) | /user/([^/]+) )$~x 最終匹配的是分組中的某一個,我們需要的子匹配也是那個分組中的,然而從結(jié)果看 preg_match($regex, /user/niki...
摘要:使開發(fā)人員可以編寫高性能的異步并發(fā),服務(wù)。使用作為網(wǎng)絡(luò)通信框架,可以使企業(yè)研發(fā)團隊的效率大大提升,更加專注于開發(fā)創(chuàng)新產(chǎn)品。總之,這個庫讓可以常駐內(nèi)存,并提供了,等功能。 swoole 使 PHP 開發(fā)人員可以編寫高性能的異步并發(fā) TCP、UDP、Unix Socket、HTTP,WebSocket 服務(wù)。Swoole 可以廣泛應(yīng)用于互聯(lián)網(wǎng)、移動通信、企業(yè)軟件、云計算、網(wǎng)絡(luò)游戲、物聯(lián)網(wǎng)(...
摘要:使開發(fā)人員可以編寫高性能的異步并發(fā),服務(wù)。使用作為網(wǎng)絡(luò)通信框架,可以使企業(yè)研發(fā)團隊的效率大大提升,更加專注于開發(fā)創(chuàng)新產(chǎn)品??傊?,這個庫讓可以常駐內(nèi)存,并提供了,等功能。 swoole 使 PHP 開發(fā)人員可以編寫高性能的異步并發(fā) TCP、UDP、Unix Socket、HTTP,WebSocket 服務(wù)。Swoole 可以廣泛應(yīng)用于互聯(lián)網(wǎng)、移動通信、企業(yè)軟件、云計算、網(wǎng)絡(luò)游戲、物聯(lián)網(wǎng)(...
摘要:原理使用模板引擎的好處是數(shù)據(jù)和視圖分離。對于循環(huán)語句怎么辦呢這個的話,請看流程控制的替代語法 原理 使用模板引擎的好處是數(shù)據(jù)和視圖分離。一個簡單的PHP模板引擎原理是 extract數(shù)組($data),使key對應(yīng)的變量可以在此作用域起效 打開輸出控制緩沖(ob_start) include模板文件,include遇到html的內(nèi)容會輸出,但是因為打開了緩沖,內(nèi)容輸出到了緩沖中 ob...
摘要:的話,是一個遵循規(guī)范微型的框架,作者這樣說大致意思的核心工作分發(fā)了請求,然后調(diào)用回調(diào)函數(shù),返回一個對象。執(zhí)行的方法時,我們從中取出的依賴,這時候,注冊的回調(diào)函數(shù)被調(diào)用,返回實例。 Slim Slim的話,是一個遵循PSR (PSR-7)規(guī)范微型的框架,作者這樣說: Slim is a PHP micro framework that helps you quickly write si...
閱讀 2484·2023-04-26 02:18
閱讀 1271·2021-10-14 09:43
閱讀 3840·2021-09-26 10:00
閱讀 6985·2021-09-22 15:28
閱讀 2550·2019-08-30 15:54
閱讀 2611·2019-08-30 15:52
閱讀 486·2019-08-29 11:30
閱讀 3475·2019-08-29 11:05