PHP入坑之 什么是路由器
11个月前
阅读 1465
评论 0
喜欢 0
###1、什么是路由
在学习路由器之前,我们必须先来了解什么是路由, 在PHP开发中,路由往往是指一段文件路径、一条URL地址,例如:
`http://127.0.0.1/index.php?controller=user&action=login`
实际上这就是一条路由,不过这条路由是最原始、并且没有经过优化的路由地址,所以他看起来非常囊肿。
而路由器的作用,就是要优化这些看起来非常囊肿的路由地址,将其美化、缩短。
###2、理论上存在的三种路由模式
```
1、原始的路由:?controller=文件夹&action=文件名&其他=参数&...
2、INFO_PATH美化路由:/文件夹/文件名/其他1/参数1/其他2/参数2/...
3、SFY_PATH简化路由:自定义路由 == 真实路由
4、CLE_PATH混合路由:INFO_PATH与SFY_PATH两种路由模式混合使用
```
可能同学们还看不懂3、4种路由模式的具体含义是什么,没关系,我们先从第2种路由开始学习,如何实现`INFO_PATH`模式,美化路由。
###3、实现路由器的关键知识点:精确获取到用户请求的URL链接
不管是`INFO_PATH、SFY_PATH`还是`CLE_PATH`路由模式,的实现前提都是需要先获取到用户当前请求的URL链接,然后再进行转发处理。
而一般获取用户的请求信息,都是使用`$_SERVER`超全局变量, 同时,由于并不是所有操作系统中都能通过`$_SERVER['PATH_INFO']`而获取用户当前请求的URL地址,所以我们需要实现三种优先级:
当`$_SERVER['PATH_INFO']`,获取不到URL地址时,尝试使用`$_SERVER['REDIRECT_PATH_INFO']`获取,如果还是获取不到,则最后再尝试使用`$_SERVER['REDIRECT_URL']`
如果三种情况都无法获取到URL地址,则代表当前服务器不支持路由美化功能,需要对服务器进行一些文件修改,不过这些知识点需要对PHP的配置文件修改有所了解才行,而且这种情况的出现几率很低,所以不用担心。
下面我们来编写一个函数,实现上面三种情况下获取URL地址:
```php
/*--------------------------------- 此处可做一些预处理操作 ---------------------------------*/
# 设置统一的浏览器编码
header("Content-type: text/html; charset=utf-8");
/*----------------------------------- 此处做单一入口加载 -----------------------------------*/
if (empty($_GET['controller'])) { die('请在地址栏中输入:controller节点 - 对应业务文件夹名称'); }
if (empty($_GET['action'])) { die('请在地址栏中输入:action节点 - 对应业务文件名称'); }
# 接收单一入口参数
$controller = $_GET['controller']; // 业务文件夹名称
$action = $_GET['action']; // 业务文件名称
# 组装文件路径
$file = "$controller/$action.php";
# 检测文件是否存在 - 不存在则直接转发到404页面
if (!file_exists($file)) {
require_once 'error/404.php';
} else {
require_once $file;
}
/*--------------------------------- 此处可做一些后处理操作 ---------------------------------*/
# 例如关闭数据库链接
# 清空session - cookie
```
这个函数的作用主要是获取url后面的字符串,例如:
`http://127.0.0.1/index.php/user/login就可以获得/user/login这段字符串`
同时这个函数对
`http://127.0.0.1/index.php?controller=user&action=login`这种原始路由无效,会返回`false`。
###4、实现INFO_PATH美化路由模式
首先,我们先从一条路由地址进行分析:
`http://127.0.0.1/index.php/ceshi-showlist-page-1.html`
从上面的路由中,我们可以截取出一段地址
`/ceshi-showlist-page-1.html`
这是我们使用` Path_Info()` 函数后,精确获取到的字符串
而其中`ceshi`是文件夹名称
`showlist`是文件名称
而`page`是`$_GET`键名
`1`是`$_GET['page']`对应的键值
`.html`是伪装的后缀名
而中间的`-`符号的是路由分隔符
下面我们再来思考下`INFO_PATH`美化路由模式的实现步骤:
```
1、我们需要先将开始的/符号去掉,可以使用ltrim()函数,
2、再将末尾的.html伪后缀名去掉,可以使用rtrim()函数,
3、最后再按-符号,将字符串分割为数组,
4、取出第一个值为controller参数,可以使用array_shift()函数,该函数的作用是取出数组中第一个元素,并从该数组中删除,
5、取出第二个值为action参数
6、然后将剩下的参数,按1 => 2 ,3 => 4的方式,重新进行$_GET赋值;
7、最后就是使用controller与action加载对应的业务文件
```
下面我们将单一入口文件中`index.php`,的核心代码,修改为`INFO_PATH`美化路由模式:
```
/*--------------------------------- 此处可做一些预处理操作 ---------------------------------*/
# 自己引入Path_Info()函数
# 设置统一的浏览器编码
header("Content-type: text/html; charset=utf-8");
# 设置后缀名
$suffix = '.html';
# 设置路由分隔符
$delimiter = '-';
# 获得路由地址
$path_info = Path_Info();
if (!$path_info) { die('路由地址为空!'); }
# 先删除左侧/符号
$path_info = ltrim($path_info, '/');
# 再删除右侧后缀名
$path_info = rtrim($path_info, $suffix);
# 再按分隔符生成数组
$route = explode($delimiter, $path_info);
if (empty($route[0])) { die('请在地址栏中输入:controller节点 - 对应业务文件夹名称'); }
if (empty($route[1])) { die('请在地址栏中输入:action节点 - 对应业务文件名称'); }
# 取出单一入口参数
$controller = array_shift($route); // 业务文件夹名称
$action = array_shift($route); // 业务文件名称
# 组装文件路径
$file = "$controller/$action.php";
# 如果URL还有后续参数,则重新赋值到$_GET中
if ( count($route) !=0 ) {
# 根据隔行算法,将参数转化为GET参数
foreach ($route as $key => $value) {
if( $key%2 == 0 ){
$_GET[$value] = '';
}else{
$_GET[$route[$key-1]] = $value;
}
}
# 打印下结果看看有没有赋值成功
var_dump($_GET);
}
# 检测文件是否存在 - 不存在则直接转发到404页面
if (!file_exists($file)) {
require_once 'error/404.php';
} else {
require_once $file;
}
/*--------------------------------- 此处可做一些后处理操作 ---------------------------------*/
# 例如关闭数据库链接
# 清空session - cookie
```
###5、实现SFY_PATH简化路由模式
首先,我们先从一条路由地址进行分析:
`http://127.0.0.1/index.php/ceshi-1-2.html`
从上面的路由中,我们可以截取出一段地址
`/ceshi-1-2.html`
这是我们使用 `Path_Info()` 函数后,精确获取到的字符串
而其中`/ceshi`是路由匹配关键词
`1`、`2`都是`$_GET[]`对应的键值
`.html`是伪装的后缀名
而中间的`-`符号的是路由分隔符
而这时候同学们可能会有以下几个疑问:
```
1、如何用/ceshi关键字来解析出对应的controller参数,和action参数?
2、1、2参数都应该赋值给哪些$_GET[]键?
```
以上的2点问题,实际上也是`SFY_PATH`简化路由模式的精髓所在,我们通过创建一个路由表文件,用于存储不同的路由,其格式如下:
```php
[
'/ceshi', // 路由匹配关键字
'ceshi/showlist', // 真实的controller和action地址
'id-page', // GET对应的参数名,使用 - 符号作为分隔符
'get|post|ajax' // 允许访问的请求类型,为空则不过滤,允许多个访问类型过滤,使用|符号分隔
],
```
通过上面的路由表,我们再来思考下`SFY_PATH`简化路由模式的实现步骤:
```
1、我们需要先将末尾的.html伪后缀名去掉,可以使用rtrim()函数,
2、再按-符号,将字符串分割为数组,
3、取出第一个值为路由匹配关键词,可以使用array_shift()函数
4、创建一个名为route.php的文件,用于存储路由表,存储格式为二维数组,其中一条数组代表一条路由地址
5、根据路由匹配关键词在路由表中匹配出对应的路由地址
6、然后将剩下的路由参数,根据提取出来的路地址中的$_GET键名,按1 => 1 ,2 => 2的方式,重新进行$_GET赋值;
7、然后再判断路由地址中对应的请求类型
8、最后就是使用controller与action加载对应的业务文件
```
下面我们先在单一入口文件`index.php`的同级目录下,创建一个`route.php`文件,并写入以下代码:
```php
return [
[ // 每个二维数组对应一条记录
'/ceshi', // 路由匹配关键字
'ceshi/showlist', // 真实的controller和action地址
'id-page', // GET对应的参数名,使用 - 符号作为分隔符
'get|post|ajax' // 允许访问的请求类型,为空则不过滤,允许多个访问类型过滤,使用|符号分隔
],
[
'/order',
'order/index',
'',
'ajax'
],
[
'/login',
'user/login',
'',
'post'
],
];
```
最后再将单一入口文件中`index.php`,的核心代码,修改为`SFY_PATH`简化路由模式:
```php
/*--------------------------------- 此处可做一些预处理操作 ---------------------------------*/
# 自己引入Path_Info()函数
# 设置统一的浏览器编码
header("Content-type: text/html; charset=utf-8");
# 设置后缀名
$suffix = '.html';
# 设置路由分隔符
$delimiter = '-';
# 获得路由地址
$path_info = Path_Info();
if (!$path_info) { die('路由地址为空!'); }
# 引入路由表
$table_route = require_once 'route.php';
# 删除右侧后缀名
$path_info = rtrim($path_info, $suffix);
# 再按分隔符生成数组
$route = explode($delimiter, $path_info);
# 取出路由关键字
$keyword = array_shift($route);
# 开始遍历路由表
$Current_Rou = '';
foreach ($table_route as $key=>$value ) {
# 找到路由
if ($value[0] == $keyword) {
$Current_Rou = $value;
break;
}
}
# 匹配不到路由直接404
if (empty($Current_Rou)) {
require_once 'error/404.php';
exit;
}
# 取出单一入口参数
# 组装文件路径
$file = $Current_Rou[1] . ".php";
# 不为空设置参数
if (!empty($Current_Rou[2]) && count($route) > 0) {
# 获得$_GET键名
$LEFT = explode('-', $Current_Rou[2]);
# 如果URL还有后续参数,则重新赋值到$_GET中
foreach ($LEFT as $key => $value) {
$_GET[$value] = $route[$key];
}
# 打印下结果看看有没有赋值成功
var_dump($_GET);
}
# 判断并过滤请求类型
if (!empty($Current_Rou[3])) {
$Http_Type = explode('|', $Current_Rou[3]);
$status = false;
foreach ( $Http_Type as $value ) {
# 类型小写化
$str = strtolower($value);
# 循环判断三种请求
if (strtolower($_SERVER['REQUEST_METHOD']) == $str) { $status = true;break; }
if (strtolower($_SERVER['REQUEST_METHOD']) == $str) { $status = true;break; }
if ($str == 'ajax' && isset($_SERVER['HTTP_X_REQUESTED_WITH']) == true && strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) == 'xmlhttprequest') { $status = true;break; }
}
if ($status == false) { die('请求类型错误'); }
}
# 检测文件是否存在 - 不存在则直接转发到404页面
if (!file_exists($file)) {
require_once 'error/404.php';
} else {
require_once $file;
}
/*--------------------------------- 此处可做一些后处理操作 ---------------------------------*/
# 例如关闭数据库链接
# 清空session - cookie
```
下面我们可以通过访问以下路由地址,查看对应的解析结果:
```
1、http://127.0.0.1/index.php/ceshi-1-2.html
2、http://127.0.0.1/index.php/order.html
3、http://127.0.0.1/index.php/login.html
```
###6、CLE_PATH混合路由模式
实际上`CLE_PATH`混合路由模式,是由`INFO_PATH`与`SFY_PATH`两种路由模式,混合而成,也称之为兼容模式。
当`SFY_PATH`模式在路由表中搜索不到对应的路由规则,则使用`CLE_PATH`模式尝试解析路由。
有兴趣研究的同学可以结合以上的思路,自己尝试实现该路由模式。
###7、路由地址中如何隐藏index.php单一入口文件名
在一条像这样的路由地址中:
`http://127.0.0.1/index.php/ceshi/showlist.html`
我们不难发现存在2个文件名`index.php`与`showlist.html`
一般时候,这种情况是不正常的,很容易误导用户,而且也不美观,所以这时候我们就要现实,如果将`index.php`这个单一入口的文件名,在路由中隐藏掉同时也不会影响程序的正常运行。
这时候,这个功能我们需要Apache的支持,我们在`index.php`文件同级下,新建一个`.htaccess`文件,并下入以下代码:
```php
Options +FollowSymlinks -Multiviews
RewriteEngine on
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(.*)$ index.php?/$1 [QSA,PT,L]
```
该文件的作用是告诉Apache,将路由中,所有错误的URL请求尝试转发到`index.php`中进行二次处理,
这样的处理机制,间接的实现了我们隐藏`index.php`单一入口文件的功能。
现在我们再来访问下面的路由,就能正确运行了:
`http://127.0.0.1/ceshi/showlist.html`
###8、隐藏index.php文件名后,Path_Info()函数的注意事项
当我们将`index.php`单一入口文件名称隐藏后,
实际上我们访问:
`http://127.0.0.1/demo/ceshi/showlist.html`
这种,带`demo`的真实目录路由时,
`Path_Info()`函数是没办法获取到`/ceshi/showlist.html`的
只能获取到`/demo/ceshi/showlist.html`,因为他没办法识别出哪一段是真实的目录名称,哪一段才是路由参数,
所以这时候同学们要自己做一下字符串处理,将其中对应的真实目录名称删除掉即可。
###9、获取当前所在目录的完整路径
使用系统自带的`__FILE__`常量,可以获取当前php文件所在的绝对路径,
如果想获取当前PHP文件所对应的上级目录,可以使用`dirname(__FILE__)`
如果是上上级,可以使用`dirname(dirname(__FILE__))`,以此类推。
###10、获取当前系统的目录分隔符
在windows系统中书写PHP代码,我们一般习惯性使用`\`符号作为目录分隔符,
但是在linux中目录的分隔符是`/`,于是PHP提供了一个内置常量`DIRECTORY_SEPARATOR`
它可以自动识别,并返回当前操作系统的所使用的目录分隔符。
为了方便朋友们学习,最后献上一个用路由模式封装成的留言板系统供大家阅读学习:[下载](https://blog.junphp.com/cangjingge/php/10.zip "下载")
© 著作权归作者所有