PHP中的自定义路由使用小结

本文由码农网 –
曾钊泳原创翻译,转载请看清文末的转载要求,欢迎参与我们的付费投稿计划澳门新浦京电子游戏 ,!

1、PATHINFO功能简述

  搞PHP的都知道ThinkPHP是一个免费开源的轻量级PHP框架,虽说轻量但它的功能却很强大。

  这也是我接触学习的第一个框架。TP框架中的URL默认模式即是PathInfo模式。这个模式很强大,每当你访问一个网站必然带有一长串参数,但是太长又显得不太友好。对于访问一个以MVC模式搭建的网站,必然带有M、C、A三个参数即module、controller、action,这些参数需要还需要用&符号隔开,假若参数量很多,就显得特别的不友好啦。然而PathInfo模式功能就是将这一长串缩短简化,让这个路径变得更加友好的显示。

传统的访问路径是这样子的:

  …..

而ThinkPHP在默认的URL模式下能够做到这样子的路径:

  …..

两者相比较很容易就得出结论:PathInfo模式下的访问路径显示更加友好!

然而在这篇文章中我所要讲述的就是如何搭建好这种友好的访问路径。

我的目标路径是这样子的(当然TP也可以很轻松做到这样子):

  …..

以上三个路径所表示的意思是一样的即都访问同一个站点,带有同样的参数

简介

我手上有个用PHP写的网站,类似于个人测试性质的站点,我独立设计,在这过程中犯了所有可能的设计和编程上的错误,同时我也收获了很多,这也促使我去学习PHP和MySQL。每当我学习新的技术或web实践时,都会想到我的网站会从中得益。正如你可以想象到的,它的可读性不强。测试网站往往在代码可读性和可维护性方面表现糟糕,尤其当你仅仅因为某些必要的功能而学习一门编程语言时,而这也正是我一直以来学习PHP的方式。

自从了解了ASP.NET
MVC,我便思考如何在这个网站上实践这套MVC模式。我喜欢这整套概念的一切,尤其是路由。但我并不想为此而重构整个网站的现有框架,担心这样一来,大部分现有代码将变成像是异物一样,不受我的控制和理解。网站已经有一个WordPress搭建的博客,通过修改模板让它看起来就像是一个完整的站点,好比在一巨大的墙上画涂鸦一样,这简直是一团糟。

然后呢?刚不久前,我学习了一些REST
API的相关知识,这是为日常工作所需而学习的。有没有一个最佳的方式去学习这些呢?我知道有个网站会从中获益。在网上阅读了一些关于在PHP环境应用RESTful
API的文章,我了解了.htaccess文件。现在我知道怎样在PHP中搭建路由,因此,也许能在测试网站实施自己设计的小型的、简单的MVC模型。

2、写作小背景

  由于最近打算模仿写一个小的框架,加强与巩固一下自己基础知识。以MVC模式进行搭建,所以就少不接触模型呀控制器呀行为之类的了。之前一直用ThinkPHP,感觉ThinkPHP中的URL中的默认模式PATHINFO很强大。所以就决定制作一个这样的功能用于自己的小框架的URL上啦。之前一直想去研究一下ThinkPHP的原码,由于没有时间,到现在都还没有去实施。打算在这个寒假里好好研究一下这个框架的原码,大学生涯最后一个寒假啦。

-为学须刚与恒,不刚则隋隳,不恒则退

  与大家共勉吧,坚持就是success嘛!好啦扯远啦,言归正传,我所制作的PATHINFO功能上和ThinkPHP是一致的,至于里面的深层原理效率问题什么的,是否和TP中的PATHINFO一样就不太清楚啦,毕竟还没有去研究TP原码,这里就按照我自己的思路来写。

设想

在PHP中搭建自定义路由的关键在于增加一个.htaccess文件,以下请看

具体实现:

我在网站文件夹目录中新增了一个”API”文件夹,在里面新建一个.htaccess文件,内容是我从Corey
Maynard写的一段文本中参考来的。

<IfModule mod_rewrite.c>
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ index.php?request=$1 [QSA,NC,L]
</IfModule>

长话短说,有了这个配置,所有以”www.yoursite.com/api/”开头或指向不存在的文件或路径的请求都会被重定向到index.php文件。$1变量值表示请求的URL地址,”request”变量名也很好理解,用$_REQUEST[‘request’]命令就可轻易获取。

另外,index.php文件和.htaccess文件在同一访问层级。

3、所涉及的核心知识

1、apache的rewrite模块。

  采用apache的rewrite模块,将所有访问这个站点的路径都只能从单一index.php入口进入。(由于apache重写规则也是一块硬骨头,在这里就不展开来细讲啦,到时候再另外写一篇文章来总结这个重写规则,与大家一起相互学习学习。作者博客:

2、正则表达式

  正则表达式的基本知识、PHP中的preg_match()函数,这个函数是制作这个功能的关键,所以需要重点理解。

3、类文件的自动载入与路径问题

  在MVC模式中最基础且需要处理的就是M、C、A三个参数,这三个参数思想贯穿于整个模式代码中。

  在这里我们需要处理的就是URL,即是我们只需要通过路径的module、controller、action就能够确定所访问的哪个类哪个控制器以及行为。对于这些类的对象object生成以及行为方法的调用都是自动的,不需要我们另外再去编写代码一一处理。

  因此对于如何精准将类文件载入以及调用方法是个很关键的步骤。PHP中内部自带有一个new
Object时自动触发的函数,那就是__autoload(),它扩展函数spl_autoload_register()注册自动加载函数。

  对于路径的问题,由于需要实现自动化即自动载入类文件等等,所以需要相对健壮的载入路径代码,让其移植性强一点。例如在Window和Linux系统下能够畅通无阻,所以需要用到PHP中的一个魔术常量__DIR__来写路径代码。

可能出现的问题?

事实上,你可能还没启用指定的module。

在WampServer环境下,我需要启用相应module来支持本机运行。一种方法是点击托盘图标进入”Apache”目录,在”Apache
modules”列表中选中rewrite_module,然后重启所有服务。另一种方法是编辑httpd.conf文件,取消下面这段配置的注释

#LoadModule rewrite_module modules/mod_rewrite.so

我想接下来要做的事情之一就是给网站做几个自测网页,好让我确定网站的负责人没有改变任何东西。这听起来可能有点偏执和可笑,但又一次,我需要临时增加
“set names latin2;”配置来检视数据库导入的数据。

如果想验证以上配置是否在服务器上启用,你可以使用这段代码:

echo in_array('mod_rewrite', apache_get_modules());

4、环境说明

  Linux虚拟机、PHP5.3.6、域名www.test2.com

接下来呢?

至此我获得一些重要的知识,让我可以为我的网站搭建新的MVC框架。使用新框架比使用现有解决方案更棒,很大程度上由于这让我在应用MVC模式的过程中获得广泛的知识。由于所有代码都是我个人的工作量,也就没有任何消耗性组件。

继续为我的网站创建REST
API。现在我才能实质性地将数据库逻辑从网页界面中抽离出来。

5、代码实例

1、建立好相应文件夹。虽然这个例子很简单但是我们也不能含糊过去,养成良好的习惯,争取早入成为大神,哈哈

  这个文件夹的话,随个人的想法来建立。要是用于框架上的话,这一步就显得很重要啦。具体可以参考各个框架的文件目录结构。我的文件目录如下图:

  澳门新浦京电子游戏 1

  才刚开始搭建的,目录很简陋,还未完善哈,毕竟还是菜鸟级别,大神们勿喷,可以的话请,还请各位指点指点哈。

  这里主要是展示一下我的文件夹,方便下面的理解分析。这个功能重点是Url.class.php文件。

2、开启apache的rewrite模块

  在相应的配置文件将其打开就好,这里就不讲解了。

  接着在根目录建立.htaccess文件,这里主要是放重写规则,如下所示:

1 <IfModule mod_rewrite.c>2    RewriteEngine on3    RewriteCond %{Request_FILENAME} !-f4    RewriteRule !.(js|ico|gif|jpg|png|css)$ /index.php5 </IfModule>

  简单解析:

    1、RewriteEngine on
开启重写 

    2、RewriteRule
重写规则,表示非上述后缀的路径都适合  

    3、RewriteCond
判断是不是文件

  这里的作用就是将所有访问www.test2.com的路径都只能index.php路径进入,即为单一入口。

3、主要代码

Url.class.php

我将此文件放入/Framework/Core文件夹中

  1 <?php  2 /*  3 *@作 者 :那一叶随风  4 *@博 客 :http://www.cnblogs.com/phpstudy2015-6/  5 *@时 间 :2017.1.3  6 */  7 class Url  8 {  9     //定义正则表达式常量 10     const REGEX_ANY="";                   #非/开头任意个任意字符 11     const REGEx_INT="";                  #数字 12     const REGEX_ALPHA="([a-zA-Z_-]+?)";           #字母 13     const REGEX_ALPHANUMERIC="([0-9a-zA-Z_-]+?)"; #任意个字母数字_- 14     const REGEX_STATIC="%s";                      #占位符 15     const ANY="*";                       #任意个非/开头字符 16  17     protected $routes=array(); #保存路径正则表达式 18  19     #添加路由正则表达式 20     public function addRoute($route) 21     { 22         $this->routes[]=$this->_ParseRoute($route); 23     } 24  25     /*private 26       @input :$route 输入路由规则 27       @output:return 返回路由正则规则 28     */ 29     private function _ParseRoute($route) 30     { 31         $parts=explode("/", $route);  #分解路由规则 32         $regex="@^";  #开始拼接正则路由规则 33         if(!$parts[0]) 34         { 35             array_shift($parts);  #除去第一个空元素 36         } 37         foreach ($parts as $part)  38         { 39             $regex.="/"; 40             $args=explode(":",$part); 41             if(!sizeof($args)==2) 42             { 43                 continue; 44             } 45             $type=array_shift($args); 46             $key=array_shift($args); 47             $this->_normalize($key);  #使参数标准化,排除其他非法符号 48             $regex.='(?P<'.$key.'>';    #为了后面preg_match正则匹配做铺垫 49             switch (strtolower($type))  50             { 51                 case 'int':      #纯数字 52                     $regex.=self::REGEX_INT; 53                     break; 54                 case 'alpha':    #纯字母 55                     $regex.=self::REGEX_ALPHA; 56                     break; 57                 case 'alphanum':  #字母数字 58                     $regex.=self::REGEX_ALPHANUMBERIC; 59                     break; 60                 default: 61                     $regex.=$type; #自定义正则表达式 62                     break; 63             } 64             $regex.=")"; 65         } 66         $regex.=self::ANY;   #其他URL参数 67         $regex.='$@u'; 68         return $regex; 69     } 70  71     /*public,将输入的URL与定义正则表达式进行匹配 72       @input  :$request 输入进来的URL 73       @output :return 成功则输出规则数组数据 失败输出false 74     */ 75     public function getRoute($request) 76     { 77         #处理request,进行参数处理,不足M、C、A,则自动补为home、index、index,即构建MVC结构URL 78         $request=rtrim($request,'/');           #除去右边多余的斜杠/ 79         $arguments=explode('/',$request); 80         $arguments=array_filter($arguments);    #除去数组中的空元素 81         $long=sizeof($arguments);               #数组中的个数 82         switch ($long)                          #判断个数,不足就补够 83         { 84             case '0': 85                 $request='/home/index/index'; 86                 break; 87             case '1': 88                 $request.='/index/index'; 89                 break; 90             case '2': 91                 $request.='/index'; 92                 break; 93         }        94         $matches=array();                       #定义匹配后存贮的数组 95         $temp=array();                          #中间缓存数组 96          97         foreach ($this->routes as $v)           #开始匹配 98         { 99             preg_match($v, $request, $temp);    #需要重点理解这个数组100             $temp?$matches=$temp:'';101         }102         if($matches)                            #判断$matches是否有数据,无返回false103         {104             foreach ($matches as $key => $value) #筛选105             {106                 if(is_int($key))107                 {108                     unset($matches[$key]);      #除去数字key元素,保留关联元素。与上面的preg_match一起理解109                 }110             }111             $result=$matches;112             if($long > sizeof($result))         #URL参数超过后的处理113             {114                 $i=1;115                 foreach ($arguments as $k => $v) 116                 {                      117                     if($k > sizeof($result))118                     {119                         if($i==1)120                         {121                             $result[$v]='';122                             $temp=$v;123                             $i=2;124                         }125                         else126                         {127                             $result[$temp]=$v;128                             $i=1;129                         }                           130                     }131                 }132             }133             return $result;134         }135         return false;136     }137     #使参数标准化,不能存在符号,只能是a-zA-Z0-9组合138     private function _normalize(&$param)139     {140         $param=preg_replace("/[^a-zA-Z0-9]/", '', $param);141     }142 }143 /*使用实例:144 include './Url.class.php';145 $router=new Url();146 $router->addRoute("/alpha:module/alpha:controller/[0-9]+?:a1a");147 $router->addRoute("/alpha:module/alpha:controller/alpha:action"); 148 $router->addRoute("/alpha:module/alpha:controller/alpha:action/(www[0-9]+?):id"); 149 $url=$_SERVER['REQUEST_URI'];150 $urls=$router->getRoute; 151 echo "<pre>";152 print_r;153 echo "</pre>";154 */155 ?>

代码功能解析:

  上面这个Url.class.php类文件代码大概可以分为两部分,在75行即方法getRoute那个地方可以将其分为上半部分和下半部分。

  上半部分是方法addRoute,是用来添加路径正则表达式的,并将其存贮在类属性$routes里。

  下半部分是方法getRoute,是用来匹配处理访问路径的。即将访问的路径传进来,再与$routes里面的正则表达式进行匹配,成功后再进一步处理,返回处理结果。

  针对上面的Url.class.php类文件,我们可以在根目录建立一个test.php测试文件或者直接在index.php文件上测试,帮助我们进一步了解这个类文件的原理与功能。(这里我就不建立test文件啦,直接在index.php文件上进行测试啦)

测试一:

index.php代码如下

1 <?php2 include "./Framework/Core/Url.class.php";   #载入类文件3 $router=new Url();4 #添加规则5 $router->addRoute("/alpha:module/alpha:controller/alpha:action"); 6 ?>

再在Url.class.php的addRoute方法中添加一个输出,用来观察,如下图:

澳门新浦京电子游戏 2

开始访问:

结果:

澳门新浦京电子游戏 3

小结:

  1、很明显,输出的类属性$routes里面存贮的是正则表达式。

  2、私有方法_ParseRoute中,调用了_normalize()方法处理$key,这个方法就是将$key除a-zA-Z0-9以外的符号过滤掉。

  3、正则表达式中,【P<‘.$key.’>】,是用来后面的preg_match匹配用的,后面讲解。

  4、switch中,就是匹配选择正则表达式,可以是已经定义好的,也可以是自己所写。例如:

     int代表self::REGEX_INT即正则表达为=》

    alpha代表self::REGEX_ALPHA即正则表达式=》([a-zA-Z_-]+?)

   
alphanum代表self::REGEX_ALPHANUMBERIC=》([0-9a-zA-Z_-]+?)

     代表采用自己所写的表达式=》例如:(www[0-9]+?)

  因此添加正则路劲addRoute的参数形式:【/int:module/alpha:controller/alphanum:action/(www[0-9]+?):id】任意组合,冒号后面的参数与preg_match共同使用,后面讲解。

  5、$regex.=self::ANY;
这里的作用是用来匹配URL路劲其他参数用的,即

测试一结束后,将Url.class.php类文件恢复原状!

测试二:

index.php代码更改如下:

 1 <?php 2 header("content-type:text/html;charset=utf8"); 3 include "./Framework/Core/Url.class.php";   #载入类文件 4 $router=new Url(); 5 #添加规则 6 $router->addRoute("/alpha:module/alpha:controller/[0-9]+?:a1a"); 7 $router->addRoute("/alpha:module/alpha:controller/alpha:action");  8 $router->addRoute("/alpha:module/alpha:controller/alpha:action/(www[0-9]+?):id"); 9 $url=$_SERVER['REQUEST_URI'];10 echo "<pre>";11 echo "index.php第一个输出<br/>";12 print_r($url);13 echo "</pre>";14 $urls=$router->getRoute($url); 15 echo "<pre>";16 echo "index.php getRoute输出结果 第五个输出<br/>";17 print_r($urls);18 echo "</pre>";die;19 ?>

再在Url.class.php的getRoute方法中添加以下输出:

  第二个输出,用来查看多个正则表达式时$routes的值,如下图:

澳门新浦京电子游戏 4

  第三个输出,如下图

澳门新浦京电子游戏 5

  第四个输出,如下图

澳门新浦京电子游戏 6

  三、四输出是用来查看理解preg_match()函数用的

开始访问:

结果与小结:

  1、输出一

澳门新浦京电子游戏 7

  此处需要理解体会$_SERVER

  2、输出二

澳门新浦京电子游戏 8

  当多个路径时,将会全部保存在$routes中

  3、输出三与四

澳门新浦京电子游戏 9

  这里需要重点讲解preg_matches()功能。

注意:

  当使用 PCRE
函数的时候,模式需要由分隔符闭合包裹。分隔符可以使任意非字母数字、非反斜线、非空白字符。如果分隔符经常在
模式内出现, 一个更好的选择就是是用其他分隔符来提高可读性。

  由此可以知道$routes中的值@的意思了,就是分隔符,只是我们经常用/而已。

  preg_matches()第一参数为正则表达式,此处我们将$routes中的放入进去。

  preg_matches()第二参数为需要匹配的数据,这里我们将传入进来的URL放进去(此处URL是输出一的值)。

  preg_matches()第二参数为不必要参数,填了此参数,则将匹配成功的值全部放入这个数组中。

  preg_matches()在PHP5.2.2是新增了一个小语法,在这里小语法很关键。

澳门新浦京电子游戏 10

  假若使用了这个小语法(?P<name>),假若这个子组匹配了的话,那么它会将匹配的数据与这个name参数形成一对关联元素,存贮于preg_matches()的第三参数数组中。这就很好的解释上述addRoute()的参数冒号后的值为何用了,以及澳门新浦京电子游戏 11的用法。

特别注意:

  foreach匹配时,假若$routes含有多个正则表示式时,它将会按顺序一个一个表达式的与URL匹配,若都匹配成功,那么后面的将会覆盖前面的值。

  4、输出五

澳门新浦京电子游戏 12

  这里就是getRoute()方法处理URL返回的结果。

测试完毕需要将Url.class.php文件恢复原样

到这里整个Url.class.php类文件讲解分析完毕,接下来就是MVC的访问啦。

下面要是简单介绍自动载入类文件,生成对象,并调用方法。

可以看看上面的文件目录来理解下面的各个文件。

index.php文件

 1 <?php 2 include './Framework/Core/Core.php'; 3  4 $router=new Url(); 5 $router->addRoute("/alpha:module/alpha:controller/[0-9]+?:a1a"); 6 $router->addRoute("/alpha:module/alpha:controller/alpha:action");  7 $router->addRoute("/alpha:module/alpha:controller/alpha:action/(www[0-9]+?):id"); 8  9 $url=$_SERVER['REQUEST_URI'];10 $urls=$router->getRoute($url); 11 $_GET['urls']=$urls;12 $m=$urls['module'];13 $c=$urls['controller'];14 $a=$urls['action'];15 if($m&&$c)16 {17     $autoload=new Autoload($m,$c);18     $autoload->PutFile();19 }20 $object=new $c;21 $object->$a();22 23 ?>

/Framework/Core/Core.php

1 <?php2 #核心文件3 #载入PHP自动加载函数文件4 include_once(__DIR__."/../Function/AutoLoad.function.php");5 ?>

/Framework/Function/AutoLoad.function.php

 1 <?php 2 #用于加载核心文件 3 spl_autoload_register('CoreFunction'); 4 function CoreFunction($classname) 5 { 6     $file=__DIR__."/../Core/".$classname.".class.php"; 7     if(is_file($file)) 8     { 9         require_once($file);10     }    11 }12 ?>

/Framework/Core/Autoload.class.php

 1 <?php 2 #用于加载控制器文件 3 class Autoload 4 { 5     private $path=''; 6     public function __construct($module,$controller) 7     { 8         $this->path=__DIR__."/../../Application/".$module."/Controller/".$controller."Controller.class.php"; 9     }10     public function PutFile()11     {12         if(is_file($this->path)) #判断文件是否存在13         {14             require_once($this->path);15         } 16         else17         {18             return false;19         }20     }21 }22 23 ?>

还有两个模块里面的控制器文件

Home中的TestController.class.php

 1 <?php 2 class Test 3 { 4     public $aa=2; 5     public function action() 6     { 7         echo "home---->test----->action"; 8         echo "<pre>"; 9         print_r($_GET);10         echo "</pre>";11     }12 }

Admin中的TestController.class.php

 1 <?php 2 class Test 3 { 4     public $aa=2; 5     public function action() 6     { 7         echo "admin---->test----->action"; 8         echo "<pre>"; 9         print_r($_GET);10         echo "</pre>";11     }12 }

开始访问:

1、

结果为:

澳门新浦京电子游戏 13

2、

结果为:

澳门新浦京电子游戏 14

大功告成,码字码到手都累啦。这里URL路径是没有处理大小写的,所以module、controller、action都是对大小写敏感的。

多一点思考、多一点琢磨、多一点敲代码,争取早日迈入大神行列!

下一次打算将它改成存储式的,将addRoute的存入在$routes的正则路径存贮在文件中,getRoute用的时候再取出来。再用apache的ab进行压力测试。

(以上是自己的一些见解,若有不足或者错误的地方请各位指出)

声明:本博客文章为原创,只代表本人在工作学习中某一时间内总结的观点或结论。转载时请在文章页面明显位置给出原文链接。

有用的链接

如果你想知道我是怎样找到解决方案,那下面是我从中获得知识的网站 

发表评论

电子邮件地址不会被公开。 必填项已用*标注