系列文章---框架篇:3.路由解析


回顾

上一节我们介绍了编写了如何处理请求与输出数据,这一节我们开始编写路由模块。


正文

还记得我们之前建立的Application在哪里吗?

我们先思考一下Application应该具备哪些功能?

首先很重要的,我们要让应用运行起来,姑且就先定义run方法。另外我们需要处理请求并且输出数据,我们再定义一个handleRequest方法。当然,我们的应用是有一些配置信息(config)的。

因为,我们不难编写出以下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
<?php

namespace Library;

use Library\Exceptions\SaiException;
use Library\Https\Request;
use Library\Https\Response;

class Application
{
private $config;

private $request;

public function __construct(Request $request, $config = [])
{
$this->config = $config;
$this->request = $request;
}

/**
* 运行应用并输出数据
* @return bool
*/
public function run()
{
try {
$response = $this->handleRequest($this->request);
$response->send();
return $response->exitStatus;
} catch (SaiException $e) {
$e->response($e->getCode(), [
'line' => $e->getLine(),
'msg' => $e->getMessage(),
'code' => $e->getCode(),
'file' => $e->getFile(),
]);
return false;
}
}

/**
* 处理请求
* @param Request $request
* @return mixed
* @throws SaiException
*/
public function handleRequest(Request $request)
{
// todo
// 返回Response对象
return $response;
}
}

这里我们看到handleRequest方法还有一部分代码为完成,回想以下流程图,这里就是我们比较核心的部分,路由处理模块。

路由解析

路由解析我们使用非常简单而常见的处理方式,不妨看几个url例子来理解一下:

route controller method
http://blog.13sai.com/ IndexController index
http://blog.13sai.com/admin AdminController index
http://blog.13sai.com/admin/test AdminController test
http://blog.13sai.com/admin/index/test Admin\IndexController test

有没有看出规律,我们会以斜杠/分割路由为几个部分,最后两部分分别是对应的控制器名称和方法名称,少于两部分默认用index,多余两部分的作为控制器的命名空间。然后我们要根据路由找到控制器构建出控制器。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
/**
* 控制器处理
* @param $route
* @return mixed
* @throws NotFoundException
*/
public function runAction($route)
{
$match = explode('/', $route);
$match = array_filter($match);

// 处理$route=/
if (empty($match)) {
$match = ['index'];
$controller = $this->createController($match);
$action = 'index';

// 处理$route=index
} elseif (count($match) < 2) {
$controller = $this->createController($match);
$action = 'index';
} else {
$action = array_pop($match);
$controller = $this->createController($match);

if (!method_exists($controller, $action)) {
throw new NotFoundException("method not found:".$action);
}
}

// 将get和post注入控制器方法中
return $controller->$action(array_merge($this->getQueryParams(), $this->getBodyParams()));
}

// app应用控制器命名空间
private $controllerNameSpace = 'App\\Https\\Controllers\\';

// 之前定义的基类控制器
private $baseController = 'Library\\Https\\Controller';

public function createController($match)
{
$controllerName = $this->controllerNameSpace;

foreach ($match as $namespace) {
$controllerName .= ucfirst($namespace).'\\';
}

$controllerName = rtrim($controllerName,'\\').'Controller';

if (!class_exists($controllerName)) {
if ($controllerName == $this->controllerNameSpace.'IndexController') {
return new $this->baseController;
}
throw new NotFoundException("controller not found:".$controllerName);
}

return new $controllerName;
}

上面是寻找控制器和方法的过程,但我们需要提前获得页面地址以解析路由。

知识点:

  1. 反斜杠:反斜线有多种用法。首先,如果紧接着是一个非字母数字字符,表明取消 该字符所代表的特殊涵义。这种将反斜线作为转义字符的用法在字符类 内部和外部都可用。
  2. array_filter ( array $array [, callable $callback [, int $flag = 0 ]] ) : array——依次将 array 数组中的每个值传递到 callback 函数。如果 callback 函数返回 true,则 array 数组的当前值会被包含在返回的结果数组中。数组的键名保留不变。代码中是过滤value为空的单元。

获取页面地址

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/**
* 返回不含参数的REQUEST_URI地址
*/
public function resolve()
{
return $this->getPathUrl();
}

private $pathUrl;

/**
* 获取请求地址
* @return bool|mixed|string
*/
public function getPathUrl()
{
if (is_null($this->pathUrl)) {
$url = trim($_SERVER['REQUEST_URI'], '/');
$index = strpos($url, '?');
$this->pathUrl = ($index > -1) ? substr($url, 0, $index) : $url;
}

return $this->pathUrl;
}

我们尽量让Application变得简洁,而路由解析又和Request关联度较高,因此我们不妨把这些方法抛出到Request对象。

知识点:

  1. strpos ( string $haystack , mixed $needle [, int $offset = 0 ] ) : int——返回 needle 在 haystack 中首次出现的数字位置,如果没找到 needle,将返回 FALSE。

上面已经解析好路由并且找到了控制器和方法。这样我们就可以完善Application的代码了。

处理请求

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public function handleRequest(Request $request)
{
$route = $request->resolve();

$response = $request->runAction($route);
/**
* 执行结果赋值给$response->data,并返回给response对象
*/
if ($response instanceof Response) {
return $response;
}

throw new SaiException('输出的内容格式错误');
}

再次需要说明的是,我们在这里仅做了json格式输出,如果有兴趣,你可以自己动手拓展一下。

另:NotFoundException继承自SaiException,代码:

1
2
3
4
5
6
7
8
9
<?php

namespace Library\Exceptions;

class NotFoundException extends SaiException
{
protected $code = 404;
}