URL 路由在前一篇讀 Slim 框架代碼(1)中已經有涉及。這一篇更詳細介紹一下 Slim 框架中對 URL 路由的處理。php
用 Slim 建立一條 URL 路由的方法:數組
$app = new \Slim\Slim(); $app->{HTTP_METHOD}('/book/:id', function($id){ // 根據書籍 $id 顯示數據 });
其中的 {HTTP_METHOD}() 能夠是 get(),post(),put(),delete() 或者是 options() 中的任意一種(GET 和 HEAD 方法都由 $app->get()
函數執行)。都是傳入兩個參數,第一個是匹配的 URI 的模式,第二個是匹配後執行的回調函數。app
如今來看 Slim 源代碼中這個函數的執行(以 $app->get()
爲例):框架
public function get() { $args = func_get_args(); return $this->mapRoute($args)->via(\Slim\Http\Request::METHOD_GET, \Slim\Http\Request::METHOD_HEAD); }
在這裏會取到 $app->get()
的全部參數而後傳到 $app->mapRoute()
,這裏會返回一個 Route 對象,接着再執行這個 Route 對象的 via()
方法,把 GET 和 HEAD 做爲參數傳入。函數
爲了方便解析,先把 mapRoute 和 via 涉及的全部源代碼列出來:post
<!--more-->this
protected function mapRoute($args) { $pattern = array_shift($args); $callable = array_pop($args); $route = new \Slim\Route($pattern, $callable); $this->router->map($route); if (count($args) > 0) { $route->setMiddleware($args); } return $route; } ... public function call() { try { ... $matchedRoutes = $this->router->getMatchedRoutes($this->request->getMethod(), $this->request->getResourceUri()); foreach ($matchedRoutes as $route) { try { $this->applyHook('slim.before.dispatch'); $dispatched = $route->dispatch(); $this->applyHook('slim.after.dispatch'); if ($dispatched) { break; } } catch (\Slim\Exception\Pass $e) { continue; } } if (!$dispatched) { $this->notFound(); } $this->applyHook('slim.after.router'); $this->stop(); } catch (\Slim\Exception\Stop $e) { ... } catch (\Exception $e) { ... } }
public function map(\Slim\Route $route) { list($groupPattern, $groupMiddleware) = $this->processGroups(); $route->setPattern($groupPattern . $route->getPattern()); $this->routes[] = $route; foreach ($groupMiddleware as $middleware) { $route->setMiddleware($middleware); } } ... public function getMatchedRoutes($httpMethod, $resourceUri, $reload = false) { if ($reload || is_null($this->matchedRoutes)) { $this->matchedRoutes = array(); foreach ($this->routes as $route) { if (!$route->supportsHttpMethod($httpMethod) && !$route->supportsHttpMethod("ANY")) { continue; } if ($route->matches($resourceUri)) { $this->matchedRoutes[] = $route; } } } return $this->matchedRoutes; }
public function __construct($pattern, $callable) { $this->setPattern($pattern); $this->setCallable($callable); $this->setConditions(self::getDefaultConditions()); } ... public function via() { $args = func_get_args(); $this->methods = array_merge($this->methods, $args); return $this; } ... public function dispatch() { foreach ($this->middleware as $mw) { call_user_func_array($mw, array($this)); } $return = call_user_func_array($this->getCallable(), array_values($this->getParams())); return ($return === false)? false : true; }
在 mapRoute 方法中首先會根據傳入的 URI 模式和回掉函數 new 一個 Route 對象,而後吧這個 Route 對象 map 到 $app->router
對象,其實就是執行 Router::map()
函數把這個 Route 對象加到 $app->router->routes
這個數組裏面。在 Router::map()
這個函數中,$groupPattern 和 $groupMiddleware 應該是用來處理路由組的,可是 Slim 文檔中貌似也沒有提到怎麼用,這裏暫時先無論了。在調用 $app->get()
方法的時候也能夠在 URI 模式和回調函數中間插入一些 中間件(Middleware),在 Route 對象中設置一些要執行的中間件,這個在本文稍後一些再具體說明。url
在 mapRoute 執行的最後,會把此時生成的 Route 對象返回,接着會執行 Route::via
方法。這個方法很簡單,就是把 GET 和 HEAD 加到 Route 對象的 method 數組成員裏。spa
最後在執行 $app->run()
方法時會調用到 $app->call()
方法(應爲 Slim 類自己也是一種中間件),這裏調用 $this->router->getMatchedRoutes
方法找出能匹配 $app->get()
的參數的全部 Route 對象,而後執行他們的 dispatch()
方法。(但一般咱們只會爲同一個 URI 的某個 HTTP_METHOD 定義一個回調函數)code
上文中調用 $this->router->getMatchedRoutes
方法找出能匹配 $app->get()
的參數的全部 Route 對象時會執行 Route.php 中的 matches()
方法。
public function matches($resourceUri) { //Convert URL params into regex patterns, construct a regex for this route, init params $patternAsRegex = preg_replace_callback( '#:([\w]+)\+?#', array($this, 'matchesCallback'), str_replace(')', ')?', (string) $this->pattern) ); if (substr($this->pattern, -1) === '/') { $patternAsRegex .= '?'; } //Cache URL params' names and values if this route matches the current HTTP request if (!preg_match('#^' . $patternAsRegex . '$#', $resourceUri, $paramValues)) { return false; } foreach ($this->paramNames as $name) { if (isset($paramValues[$name])) { if (isset($this->paramNamesPath[ $name ])) { $this->params[$name] = explode('/', urldecode($paramValues[$name])); } else { $this->params[$name] = urldecode($paramValues[$name]); } } } return true; } ... protected function matchesCallback($m) { $this->paramNames[] = $m[1]; if (isset($this->conditions[ $m[1] ])) { return '(?P<' . $m[1] . '>' . $this->conditions[ $m[1] ] . ')'; } if (substr($m[0], -1) === '+') { $this->paramNamesPath[ $m[1] ] = 1; return '(?P<' . $m[1] . '>.+)'; } return '(?P<' . $m[1] . '>[^/]+)'; }
matches()
方法會把模式中的全部 #:([\w]+)\+?#
模式替換成 (?P<' . $m[1] . '>[^/]+)
(僅匹配一級目錄)或 (?P<' . $m[1] . '>.+)
(匹配每一級目錄),若是有設定某個參數的 condition,會直接用 condition 替換成 (?P<' . $m[1] . '>' . $this->conditions[ $m[1] ] . ')'
。這裏 (?P<name>.+)
用來命名分組。
舉個例子,若是 Route 的模式爲 /:id/:name
,那它會被替換成 /(?P<id>[^/]+)/(?P<name>[^/]+)
。假設咱們訪問的 URL 爲 /ljie/Rocky
,那麼在執行完 preg_match('#^' . $patternAsRegex . '$#', $resourceUri, $paramValues)
這條語句後會獲得
$paramValues = array( "id" => "ljie", "name" => "Rocky" );
路由中間件是一系列函數,把 Route 對象做爲輸入參數,期間經過 $app = \Slim\Slim::getInstance();
的到 $app 實例,能夠作一些過濾,驗證及其餘一些公共的邏輯。在 Route.php 中的 dispatch()
方法中會執行這些中間件
public function dispatch() { foreach ($this->middleware as $mw) { call_user_func_array($mw, array($this)); } ... }