一、PATHINFO功能簡述
搞PHP的都知道ThinkPHP是一個免費開源的輕量級PHP框架,雖說輕量但它的功能卻很強大。這也是我接觸學習的第一個框架。TP框架中的URL默認模式即是PathInfo模式。這個模式很強大,每當你訪問一個網站必然帶有一長串參數,但是太長又顯得不太友好。對於訪問一個以MVC模式搭建的網站,必然帶有M、C、A三個參數即module、controller、action,這些參數需要還需要用&符號隔開,假若參數量很多,就顯得特別的不友好啦。然而PathInfo模式功能就是將這一長串縮短簡化,讓這個路徑變得更加友好的顯示。
傳統的訪問路徑是這樣子的:
http://www.example.com/index.php?m=module&c=controller&a=action&var1=vaule1&var2=vaule2.....
而ThinkPHP在默認的URL模式下能夠做到這樣子的路徑:
http://www.example.com/index.php?module/controller/action/var1/vaule1/var2/value2.....
兩者相比較很容易就得出結論:PathInfo模式下的訪問路徑顯示更加友好!
然而在這篇文章中我所要講述的就是如何搭建好這種友好的訪問路徑。
我的目標路徑是這樣子的(當然TP也可以很輕松做到這樣子):
http://www.example.com/module/controller/action/var1/vaule1/var2/value2.....
以上三個路徑所表示的意思是一樣的即都訪問同一個站點,帶有同樣的參數
二、寫作小背景
放松放松,來個小小的過渡,哈哈。由於最近打算搭建一個mini型的超級超級小的框架(此處犯了意思相同的語法錯誤,得零分,哈哈),加強與鞏固一下自己基礎知識。以MVC模式進行搭建,所以就少不接觸模型呀控制器呀行為之類的了。之前一直用ThinkPHP,感覺ThinkPHP中的URL中的默認模式PATHINFO很強大。所以就決定制作一個這樣的功能用於自己的小框架的URL上啦。之前一直想去研究一下ThinkPHP的原碼,由於沒有時間,到現在都還沒有去實施。打算在這個寒假裡好好研究一下這個框架的原碼,大學生涯最後一個寒假啦。
----為學須剛與恆,不剛則隋隳,不恆則退。
與大家共勉吧,堅持就是success嘛!好啦扯遠啦,言歸正傳,我所制作的PATHINFO功能上和ThinkPHP是一致的,至於裡面的深層原理效率問題什麼的,是否和TP中的PATHINFO一樣就不太清楚啦,畢竟還沒有去研究TP原碼,這裡就按照我自己的思路來寫。
三、所涉及的核心知識
1、apache的rewrite模塊。
采用apache的rewrite模塊,將所有訪問這個站點的路徑都只能從單一index.php入口進入。(由於apache重寫規則也是一塊硬骨頭,在這裡就不展開來細講啦,到時候再另外寫一篇文章來總結這個重寫規則,與大家一起相互學習學習。作者博客:http://www.cnblogs.com/phpstudy2015-6/)
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__來寫路徑代碼。
四、環境說明
Linux虛擬機、PHP5.3.6、域名www.test2.com
五、代碼實例
1、建立好相應文件夾。雖然這個例子很簡單但是我們也不能含糊過去,養成良好的習慣,爭取早入成為大神,哈哈
這個文件夾的話,隨個人的想法來建立。要是用於框架上的話,這一步就顯得很重要啦。具體可以參考各個框架的文件目錄結構。我的文件目錄如下圖:

才剛開始搭建的,目錄很簡陋,還未完善哈,畢竟還是菜鳥級別,大神們勿噴,可以的話請,還請各位指點指點哈。
這裡主要是展示一下我的文件夾,方便下面的理解分析。這個功能重點是Url.class.php文件。
2、開啟apache的rewrite模塊
在相應的配置文件將其打開就好,這裡就不講解了。
接著在根目錄建立.htaccess文件,這裡主要是放重寫規則,如下所示:
1 <IfModule mod_rewrite.c>
2 RewriteEngine on
3 RewriteCond %{Request_FILENAME} !-f
4 RewriteRule !\.(js|ico|gif|jpg|png|css)$ /index.php
5 </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="([0-9]+?)"; #數字
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是否有數據,無返回false
103 {
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 else
126 {
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($url);
151 echo "<pre>";
152 print_r($urls);
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 <?php
2 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方法中添加一個輸出,用來觀察,如下圖:

開始訪問:http://www.test2.com/
結果:

小結:
1、很明顯,輸出的類屬性$routes裡面存貯的是正則表達式。
2、私有方法_ParseRoute中,調用了_normalize()方法處理$key,這個方法就是將$key除a-zA-Z0-9以外的符號過濾掉。
3、正則表達式中,【P<'.$key.'>】,是用來後面的preg_match匹配用的,後面講解。
4、switch中,就是匹配選擇正則表達式,可以是已經定義好的,也可以是自己所寫。例如:
int代表self::REGEX_INT即正則表達為=》([0-9]+?)
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路勁其他參數用的,即http://www.example.com/module/controller/action/var1/values1中,action後面var1、values1等等參數。
測試一結束後,將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的值,如下圖:

第三個輸出,如下圖

第四個輸出,如下圖

三、四輸出是用來查看理解preg_match()函數用的
開始訪問:http://www.test2.com/m/c/a/var/value
結果與小結:
1、輸出一

此處需要理解體會$_SERVER
2、輸出二

當多個路徑時,將會全部保存在$routes中
3、輸出三與四

這裡需要重點講解preg_matches()功能。
注意:
當使用 PCRE 函數的時候,模式需要由分隔符閉合包裹。分隔符可以使任意非字母數字、非反斜線、非空白字符。如果分隔符經常在 模式內出現, 一個更好的選擇就是是用其他分隔符來提高可讀性。
由此可以知道$routes中的值@的意思了,就是分隔符,只是我們經常用/而已。
preg_matches()第一參數為正則表達式,此處我們將$routes中的放入進去。
preg_matches()第二參數為需要匹配的數據,這裡我們將傳入進來的URL放進去(此處URL是輸出一的值)。
preg_matches()第二參數為不必要參數,填了此參數,則將匹配成功的值全部放入這個數組中。
preg_matches()在PHP5.2.2是新增了一個小語法,在這裡小語法很關鍵。

假若使用了這個小語法(?P<name>),假若這個子組匹配了的話,那麼它會將匹配的數據與這個name參數形成一對關聯元素,存貯於preg_matches()的第三參數數組中。這就很好的解釋上述addRoute()的參數冒號後的值為何用了,以及
的用法。
特別注意:
foreach匹配時,假若$routes含有多個正則表示式時,它將會按順序一個一個表達式的與URL匹配,若都匹配成功,那麼後面的將會覆蓋前面的值。
4、輸出五

這裡就是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 <?php 2 #核心文件 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 else
17 {
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、http://www.test2.com/Home/Test/action/var1/vaule1/var2/value2
結果為:

2、http://www.test2.com/Admin/Test/action/var1/vaule1/var2/value2
結果為:

大功告成,碼字碼到手都累啦。這裡URL路徑是沒有處理大小寫的,所以module、controller、action都是對大小寫敏感的。
多一點思考、多一點琢磨、多一點敲代碼,爭取早日邁入大神行列!
下一次打算將它改成存儲式的,將addRoute的存入在$routes的正則路徑存貯在文件中,getRoute用的時候再取出來。再用並發測試一下這兩個的效率。
(以上是自己的一些見解,若有不足或者錯誤的地方請各位指出)
作者:壹葉隨風
聲明:本博客文章為原創,只代表本人在工作學習中某一時間內總結的觀點或結論。轉載時請在文章頁面明顯位置給出原文鏈接。