routing路由的作用是将请求分发到不同的controller,基于正则匹配的原则。接下来,我们实现一个简单的路由器,它能够正确调用静态路由的回调(没有占位符)。对于带占位符的路由,正确调用回调时会传入占位符参数,例如对于路由:/user/{id},当请求为/user/23时,传入参数$args结构为['id'=>'23']大意是我们需要管理每条路由的信息:http方法($method)、路由字符串($route)、回调($callback),所以需要一个addRoute方法,还有short方法get,post(即写$method)for/user/{id}带占位符的路由字符串,提取占位符,然后将占位符转为正则字符串代码讲解路由分类对于注册的路由,需要分为两种categories(下面说的$uri是指$_SERVER['REQUEST_URI']去掉查询字符串的值)静态路由(即不带占位符的路由,如/articles)routingwithparameters(带占位符的路由,如/user/{uid})其实这是很明显的,对于静态路由,我们只需要directly比较是否等于$uri,对于带参数的路由,如/user/{uid},我们需要在注册时提取占位符名称,将{**}部分替换为常规字符串如([a-zA-Z0-9_]+),因为要进行分组捕获,所以用(),取出占位符对应的值。在Dispatcher.php中有两个数组$staticRoutes$methodToRegexToRoutesMap分别对应静态路由和带参路由。另外需要注意的是,这两个数组是二维数组,第一维存放http方法,第二维存放正则字符串(静态路由本身就是这个值,而带参路由是后面的值代替占位符),最后的值是一个Route对象Route类这个类比较容易理解,用来存放注册路由的一些信息。$httpMethod:HTTP方法,包括GET、POST、PUT、PATCH、HEAD$regex:路由的正则表达式,带参数的路由是占位符替换后的值,静态路由本身就是这个值$variables:集合路由的占位符参数,static路由是一个空数组$handler:路由要回调的对象当然这个类还可以存储多一点的信息,比如最原始的字符串(/user/{uid})的带参数的路由,这里根据http方法Fetch数据做一个简单的分发过程,因为第一个维度是http方法逐一匹配静态路由。对于带参数的路由,先把所有的正则表达式组合成一个大的正则串,而不是一个一个去匹配(这样效率低)第一步很简单,主要说明第二步是针对三个独立的正则串(分隔符是~):~^/user/([^/]+)/(\d+)$~~^/user/(\d+)$~~^/user/([^/]+)$~我们可以组合他们得到~^(?:/user/([^/]+)/(\d+)|/user/(\d+)|/user/([^/]+))$~x?:它是一个非捕获组。这种转变非常简单。我们怎么知道正则表达式被匹配了呢??例如:preg_match($regex,'/user/nikic',$matches);=>["/user/nikic",#精确匹配"","",#第一个(空)"",#第二个(empty)"nikic",#第三个(用过)]可以看到第一个非空的位置可以推断出匹配的是哪条路由(需要剔除第一个完全匹配的),我们需要一个数组来映射它[1=>['handler0',['name','id']],3=>['手ler1',['id']],4=>['handler2',['name']],]1因为第一个匹配被排除3因为第一个路由有两个占位符4因为第二个路由上面有一个数组占位符,我们可以注册methodToRegexToRoutesMap来形成这个数量,我是这样写的[$this->methodToRegexToRoutesMap[$httpMethod][$regex]->handler,$this->methodToRegexToRoutesMap[$httpMethod][$regex]->变量,];$index+=count($this->methodToRegexToRoutesMap[$httpMethod][$regex]->variables);}最后调用回调函数,返回一个数组。第一个值用来判断是否最终找到了Route.php类的实现httpMethod=$httpMethod;$this->handler=$handler;$this->regex=$regex;$this->变量=$变量;}/***测试这个路由是否匹配给定的字符串。**@paramstring$str**@returnbool*/publicfunctionmatches($str){$regex='~^'.$这个->正则表达式。'$~';返回(布尔)preg_match($regex,$str);}}Dispatcher.php1){preg_match_all('~\{([a-zA-Z0-9_]+?)\}~',$route,$matchesVariables);返回[preg_replace('~{[a-zA-Z0-9_]+?}~','([a-zA-Z0-9_]+)',$route),$matchesVariables[1],];}else{return[$route,[],];}}thrownew\LogicException('注册路由失败,模式不合法');}/***注册路径由*@param$httpMethodstring|字符串[]*@param$route*@param$handler*/publicfunctionaddRoute($httpMethod,$route,$handler){$routeData=$this->parse($route);foreach((array)$httpMethodas$method){if($this->isStaticRoute($routeData)){$this->addStaticRoute($httpMethod,$routeData,$handler);}else{$this->addVariableRoute($httpMethod,$routeData,$handler);}}}privatefunctionisStaticRoute($routeData){returncount($routeData[1])===0;}私有函数addStaticRoute($httpMethod,$routeData,$handler){$routeStr=$routeData[0];if(isset($this->staticRoutes[$httpMethod][$routeStr])){thrownew\LogicException(sprintf('无法为方法“%s”注册两条匹配“%s”的路由',$routeStr,$httpMethod));}if(isset($this->methodToRegexToRoutesMap[$httpMethod])){foreach($this->methodToRegexToRoutesMap[$httpMethod]as$route){if($route->matches($routeStr)){thrownew\LogicException(sprintf('静态路由"%s"被之前的defi隐藏了为方法“%s”设置变量路由“%s”',$routeStr,$route->regex,$httpMethod));$this->staticRoutes[$httpMethod][$routeStr]=$handler;}privatefunctionaddVariableRoute($httpMethod,$routeData,$handler){list($regex,$variables)=$routeData;if(isset($this->methodToRegexToRoutesMap[$httpMethod][$regex])){thrownew\LogicException(sprintf('无法为方法“%s”注册两条匹配“%s”的路由',$regex,$httpMethod));}$this->methodToRegexToRoutesMap[$httpMethod][$regex]=newRoute($httpMethod,$handler,$regex,$variables);}publicfunctionget($route,$handler){$this->addRoute('GET',$route,$handler);}publicfunctionpost($route,$handler){$this->addRoute('POST',$route,$handler);}publicfunctionput($route,$handler){$this->addRoute('PUT',$route,$handler);}publicfunctiondelete($route,$handler){$this->addRoute('DELETE',$route,$handler);}publicfunctionpatch($route,$handler){$this->addRoute('PATCH',$route,$handler);}publicfunctionhead($route,$handler){$this->addRoute('HEAD',$route,$handler);}/***分发*@param$httpMethod*@param$uri*/publicfunctiondispatch($httpMethod,$uri){$staticRoutes=array_keys($this->staticRoutes[$httpMethod]);foreach($staticRoutesas$staticRoute){if($staticRoute===$uri){返回[self::FOUND,$this->staticRoutes[$httpMethod][$staticRoute],[]];}}$routeLookup=[];$索引=1;$regexes=array_keys($this->methodToRegexToRoutesMap[$httpMethod]);foreach($正则表达式为$regex){$routeLookup[$index]=[$this->methodToRegexToRoutesMap[$httpMethod][$regex]->handler,$this->methodToRegexToRoutesMap[$httpMethod][$regex]->变量,];$index+=count($this->methodToRegexToRoutesMap[$httpMethod][$regex]->变量);$regexCombined='~^(?:'.implode('|',$regexes).')$~';如果(!preg_match($regexCombined,$uri,$matches)){返回[self::NOT_FOUND];}for($i=1;''===$matches[$i];++$i);列表($handler,$varNames)=$routeLookup[$i];$变量=[];foreach($varNamesas$varName){$vars[$varName]=$matches[$i++];}返回[self::FOUND,$handler,$vars];}}配置nginx.conf重写到index.phplocation/{try_files$uri$uri//index.php$is_args$args;#将PHP脚本传递给监听127.0.0.1:9000的FastCGI服务器#location~\.php${fastcgi_pass127.0.0.1:9000;fastcgi_indexindex.php;fastcgi_paramSCRIPT_FILENAME$document_root$fastcgi_script_name;包括fastcgi_params;}}composer.json自动加载{"name":"salmander/route","require":{},"autoload":{"psr-4":{"SalamanderRoute\\":"SalamanderRoute/"}}}最终使用index.phpget('/',function(){echo'helloworld';});$dispatcher->get('/user/{id}',function($args){echo"user{$args['id']}visit";});//从某处获取方法和URI$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]){caseDispatcher::NOT_FOUND:echo'404未找到';休息;caseDispatcher::FOUND:$handler=$routeInfo[1];$vars=$routeInfo[2];$处理程序($变量);休息;}
