请求路由
一个HTTP请求由哪个PHP文件响应,实际上是Nginx或Apache决定的,默认情况下,服务器会去对应的文件目录找对应的PHP文件代码,让PHP解释器去执行。比如我们的请求为/backend/blogs.php,那它就需要真的在工程目录下,建立backend文件夹和blogs.php文件,对于只有几个页面的小项目,这种做法没什么问题,但这不符合现代网站的需求。比如我们的一个REST风格的请求形式/api/blogs/1,其中1只是个查询参数,我们不可能创建一个1.php。
现在的Web框架,都有一个可配置的路由功能,对应URL由哪段代码相应,都应该是可配置的,而不是靠文件夹目录结构决定的。
Nginx配置
我们以Nginx为例进行配置,Apache的配置不同,但是原理是一样的。这里我们直接参考Laravel的路由配置:
server {
listen 80;
server_name example.com;
root /example.com/public;
add_header X-Frame-Options "SAMEORIGIN";
add_header X-XSS-Protection "1; mode=block";
add_header X-Content-Type-Options "nosniff";
index index.html index.htm index.php;
charset utf-8;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location = /favicon.ico { access_log off; log_not_found off; }
location = /robots.txt { access_log off; log_not_found off; }
error_page 404 /index.php;
location ~ \.php$ {
fastcgi_pass unix:/var/run/php/php7.2-fpm.sock;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
include fastcgi_params;
}
location ~ /\.(?!well-known).* {
deny all;
}
}
其中我们主要关注下面:
location / {
try_files $uri $uri/ /index.php?$query_string;
}
上面配置的功能其实就是把所有请求连同请求参数发给index.php,而具体加载哪些代码,执行哪个函数或方法,则是我们代码决定的。
路由匹配和逻辑执行
我们代码包含如下两个文件:

其中index.php是我们代码的入口文件,Router.php是包含路由功能的一个工具类,由index.php引用。
Router.php
<?php
class Router
{
private $arr = array();
public function register($uri, $callback)
{
array_push($this->arr, array('uri' => $uri, 'callback' => $callback));
}
public function dispatch($uri)
{
for ($i = 0; $i < count($this->arr); $i++) {
$rule = $this->arr[$i];
if (strpos($uri, $rule['uri']) === 0) {
$rule['callback']();
break;
}
}
}
}
其中register($uri, $callback)方法用于注册路由,被注册的路由配置维护在数组$arr中,函数参数是请求需要匹配的URL和回调函数。dispatch($uri)用于转发请求到对应的函数上执行。
index.php
<?php
include_once 'Router.php';
// 定义处理函数
$func1 = function () {
echo '页面1';
};
$func2 = function () {
echo '页面2';
};
// 注册路由
$router = new Router();
$router->register('/page1', $func1);
$router->register('/page2', $func2);
// 请求处理
$req_uri = $_SERVER['REQUEST_URI'];
$router->dispatch($req_uri);
上面代码首先定义了处理函数,这里只是个例子,在实际开发中,处理请求的应该是MVC的控制器组件,不应该出现在框架的入口函数中。
之后注册了两个路由/page1和/page2,将它们对应到入口函数上。
最后要注意的是我们的请求虽然被Nginx重写到index.php上,但我们依然可以通过Nginx为我们设定的REQUEST_URI这个HTTP请求头来获取真实的请求URI,我们根据这个URI使用代码进行判断处理转发到哪个函数上就可以了。
总结
实际上,上面代码功能还很不全面,路由只能靠前缀匹配,也没做REST参数的处理,但实际上,各大框架路由实现的基本原理都是一样的,我们自己写路由组件可能漏洞比较多,但路由其实有很多现成的轮子可用,这里就不多介绍了。