PHP DIY系列–一起手写一个api框架
回顾 上一节我们介绍了编写了如何处理请求与输出数据,这一节我们开始编写路由模块。
正文 还记得我们之前建立的Application在哪里吗?
我们先思考一下Application应该具备哪些功能?
首先很重要的,我们要让应用运行起来,姑且就先定义run方法。另外我们需要处理请求并且输出数据,我们再定义一个handleRequest方法。当然,我们的应用是有一些配置信息(config)的。
因为,我们不难编写出以下代码:
<?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例子来理解一下:
有没有看出规律,我们会以斜杠/分割路由为几个部分,最后两部分分别是对应的控制器名称和方法名称,少于两部分默认用index,多余两部分的作为控制器的命名空间。然后我们要根据路由找到控制器构建出控制器。
/** * 控制器处理 * @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; }
上面是寻找控制器和方法的过程,但我们需要提前获得页面地址以解析路由。
知识点:
反斜杠:反斜线有多种用法。首先,如果紧接着是一个非字母数字字符,表明取消 该字符所代表的特殊涵义。这种将反斜线作为转义字符的用法在字符类 内部和外部都可用。
array_filter ( array $array [, callable $callback [, int $flag = 0 ]] ) : array——依次将 array 数组中的每个值传递到 callback 函数。如果 callback 函数返回 true,则 array 数组的当前值会被包含在返回的结果数组中。数组的键名保留不变。代码中是过滤value为空的单元。
获取页面地址 /** * 返回不含参数的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对象。
知识点:
strpos ( string $haystack , mixed $needle [, int $offset = 0 ] ) : int——返回 needle 在 haystack 中首次出现的数字位置,如果没找到 needle,将返回 FALSE。
上面已经解析好路由并且找到了控制器和方法。这样我们就可以完善Application的代码了。
处理请求 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,代码:
<?php namespace Library \Exceptions ;class NotFoundException extends SaiException { protected $code = 404 ; }