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)); } ... }