公司内部系统代码重构使用了 thinkPHP5.0 ,在使用tp框架时,就学习了tp 框架的路由实现。
一、 PATH_INFO 模式是什么?
PATH_INFO 模式是伪静态的一种。
我们可以使用 PATH_INFO 来代替 Rewrite 来实现伪静态页面, 另外不少 PHP 框架也使用PATH_INFO 来作为路由载体
伪静态页面是静态 URL 与动态 URL 互通的一个桥梁,它是指动态网址通过 URL 重写的手段去掉其动态参数,使 URL 静态化,但在实际的网页目录中并没有重写 URL。
简单来说,伪静态 URL 就是通过服务器转换伪装文件名或地址,使该页面类似于静态页面,但服务器上没有独立存在的文件,其本质还是动态页面。
PATH_INFO是服务器状态中的一个参数,PHP通过$_SERVER[‘PATH_INFO’]可以查看内容。
二、 thinkPHP 路由源码分析
thinkPHP 源码首先监听调度信息,发起调度请求;并记录请求信息
// 监听app_dispatch
$this->hook->listen('app_dispatch');
$dispatch = $this->dispatch;
if (empty($dispatch)) {
// 路由检测
$dispatch = $this->routeCheck()->init();
}
// 记录当前调度信息
$this->request->dispatch($dispatch);
// 记录路由和请求信息
if ($this->appDebug) {
$this->log('[ ROUTE ] ' . var_export($this->request->routeInfo(), true));
$this->log('[ HEADER ] ' . var_export($this->request->header(), true));
$this->log('[ PARAM ] ' . var_export($this->request->param(), true));
}
下面分享一些,相关的路由方法
- 获取 pathinfo
/**
* 获取当前请求URL的pathinfo信息(含URL后缀)
* @access public
* @return string
*/
public function pathinfo()
{
if (is_null($this->pathinfo)) {
if (isset($_GET[$this->config['var_pathinfo']])) {
// 判断URL里面是否有兼容模式参数
$pathinfo = $_GET[$this->config['var_pathinfo']];
unset($_GET[$this->config['var_pathinfo']]);
unset($this->get[$this->config['var_pathinfo']]);
} elseif ($this->isCli()) {
// CLI模式下 index.php module/controller/action/params/...
$pathinfo = isset($_SERVER['argv'][1]) ? $_SERVER['argv'][1] : '';
} elseif ('cli-server' == PHP_SAPI) {
$pathinfo = strpos($this->server('REQUEST_URI'), '?') ? strstr($this->server('REQUEST_URI'), '?', true) : $this->server('REQUEST_URI');
} elseif ($this->server('PATH_INFO')) {
$pathinfo = $this->server('PATH_INFO');
}
// 分析PATHINFO信息
if (!isset($pathinfo)) {
foreach ($this->config['pathinfo_fetch'] as $type) {
if ($this->server($type)) {
$pathinfo = (0 === strpos($this->server($type), $this->server('SCRIPT_NAME'))) ?
substr($this->server($type), strlen($this->server('SCRIPT_NAME'))) : $this->server($type);
break;
}
}
}
if (!empty($pathinfo)) {
unset($this->get[$pathinfo], $this->request[$pathinfo]);
}
$this->pathinfo = empty($pathinfo) || '/' == $pathinfo ? '' : ltrim($pathinfo, '/');
}
return $this->pathinfo;
}
- 导入配置文件
/**
* 导入配置文件的路由规则
* @access public
* @param array $rules 路由规则
* @param string $type 请求类型
* @return void
*/
public function import(array $rules, $type = '*')
{
// 检查域名部署
if (isset($rules['__domain__'])) {
foreach ($rules['__domain__'] as $key => $rule) {
$this->domain($key, $rule);
}
unset($rules['__domain__']);
}
// 检查变量规则
if (isset($rules['__pattern__'])) {
$this->pattern($rules['__pattern__']);
unset($rules['__pattern__']);
}
// 检查路由别名
if (isset($rules['__alias__'])) {
foreach ($rules['__alias__'] as $key => $val) {
$this->alias($key, $val);
}
unset($rules['__alias__']);
}
// 检查资源路由
if (isset($rules['__rest__'])) {
foreach ($rules['__rest__'] as $key => $rule) {
$this->resource($key, $rule);
}
unset($rules['__rest__']);
}
// 检查路由规则(包含分组)
foreach ($rules as $key => $val) {
if (is_numeric($key)) {
$key = array_shift($val);
}
if (empty($val)) {
continue;
}
if (is_string($key) && 0 === strpos($key, '[')) {
$key = substr($key, 1, -1);
$this->group($key, $val);
} elseif (is_array($val)) {
$this->rule($key, $val[0], $type, $val[1], isset($val[2]) ? $val[2] : []);
} else {
$this->rule($key, $val, $type);
}
}
}
- 解析路由
/**
* 解析匹配到的规则路由
* @access public
* @param Request $request 请求对象
* @param string $rule 路由规则
* @param string $route 路由地址
* @param string $url URL地址
* @param array $option 路由参数
* @param array $matches 匹配的变量
* @return Dispatch
*/
public function parseRule($request, $rule, $route, $url, $option = [], $matches = [])
{
if (is_string($route) && isset($option['prefix'])) {
// 路由地址前缀
$route = $option['prefix'] . $route;
}
// 替换路由地址中的变量
if (is_string($route) && !empty($matches)) {
$search = $replace = [];
foreach ($matches as $key => $value) {
$search[] = '<' . $key . '>';
$replace[] = $value;
$search[] = ':' . $key;
$replace[] = $value;
}
$route = str_replace($search, $replace, $route);
}
// 解析额外参数
$count = substr_count($rule, '/');
$url = array_slice(explode('|', $url), $count + 1);
$this->parseUrlParams($request, implode('|', $url), $matches);
$this->vars = $matches;
$this->option = $option;
$this->doAfter = true;
// 发起路由调度
return $this->dispatch($request, $route, $option);
}
- 自动定位模型和类
/**
* 自动定位控制器类
* @access protected
* @param string $module 模块名
* @param array $path URL
* @return string
*/
protected function autoFindController($module, &$path)
{
$dir = $this->app->getAppPath() . ($module ? $module . '/' : '') . $this->rule->getConfig('url_controller_layer');
$suffix = $this->app->getSuffix() || $this->rule->getConfig('controller_suffix') ? ucfirst($this->rule->getConfig('url_controller_layer')) : '';
$item = [];
$find = false;
foreach ($path as $val) {
$item[] = $val;
$file = $dir . '/' . str_replace('.', '/', $val) . $suffix . '.php';
$file = pathinfo($file, PATHINFO_DIRNAME) . '/' . Loader::parseName(pathinfo($file, PATHINFO_FILENAME), 1) . '.php';
if (is_file($file)) {
$find = true;
break;
} else {
$dir .= '/' . Loader::parseName($val);
}
}
if ($find) {
$controller = implode('.', $item);
$path = array_slice($path, count($item));
} else {
$controller = array_shift($path);
}
return $controller;
}