文件源代码分析
写在前面
这个是数据库的api文件,如果是跟前端配合的话,那么前端最起码需要做两方面的工作。
- 写一个orm类,代理底层Ajax的跟Api.php沟通。具体参考一下,odata提供的js库。
- Api简化了后端的工作,但是前端相关接口处理工作,应该独立成一个库文件,这样方便多端复用。
build.php
这个是打包工具,将src文件夹中的php文件打包成一个文件夹。其中,src/index.php中有一行,不要再格式化此行代码,估计打包的时候严格的替换掉。打包的作用有两个:
- 将多个php文件合成一个文件。
- 删除php文件夹中的一些不必要的内容。如命名空间、<?php 等符号。
思考题:
- 此项目的源代码解释,非常的少。难道是为了迎合build.php的原因,减少打包删除注释的难度?又或是太简单了,不需要解释?
- build的顺序,越是深层次的文件,越有限打包,这样能合理的控制,各个文件打包的顺序。
- 此api,build之后,所有的文件都非懒加载,如果配合swoole、workman等框架,常驻内存,效果是否更好?
思考:如果不需要支持sqlite、post等数据库,那么打包是否精减,不再包含其他的数据库的函数,这样减少体积?
源码分析
index.php
所有的文件都在PhpCrudApi中,在最外层有个index.php文件,这个是配置文件,所有的设置都在这个里面。通过在这个文件中进行设置,然后再通过build.php进行打包,能测试,能测出数据库连接是否正确(非主要),否则生成一个tmp_api文件。
//自动加载,如果是打包成独立文件,则不需要这端代码。
spl_autoload_register(function ($class) {include str_replace('\\', '/', __DIR__ . "/$class.php");});
上面的代码,要进行反思、总结。如何像空间一样,不需要手动include文件。如果自己写框架,是不是要用上上面的这个函数。
主要流程:
配置config对象→ Request对象→Api对象 然后api->handle处理生成一个response。response进行输出。
所以接着研究config、Request、Api对象。
Config.php
配置对象,即index.php中的配置。主要功能,储存所有的配置。不要在这个对象里面进行设置,试过,在这个里面进行了数据设置好像也没有用。但是这个框架里面的默认设置都是在这个里面的。
在这个对象的构造函数中,步骤如下:
- 优先获取到当前是何种数据库,getDefaultDriver,如果没有设置,默认则为mysql。
- 根据上一步判断数据库类型的结果,获取默认的参数defaults,’driver’,’address’ => $this->getDefaultAddress($driver),’port’ => $this->getDefaultPort($driver),
- 合并结果三者结果。类默认的参数,获取到的参数,用户在index.php中的参数,即array_merge($this->values, $defaults, $values)。大概的意思是,怕用户设置错误。测试过array_merge,最后的优先级大,即会覆盖调前面的同名键名。所以,这功能:如果在index.php中进行了设置,则用,没有的则用类默认行为。
//测试例子 $a=array('name'=>'zhangsan1'); $b=array('name'=>'zhangsan2'); $c=array('name'=>'zhangsan3'); var_dump(array_merge($a,$b,$c)); - 解析中间件设置。 parseMiddlewares。这个函数,可以看到php7中,可以指定返回类型了。功能:上一步得到的设置只,middlewares 通过逗号分隔,忽略单词两边的空格。array_map(‘trim’, explode(‘,’, $values[‘middlewares’]))。还要考虑这个配置项中如果有句号.问题。这个函数的功能,有待深入了解。暂略。
- 比较生成的配置的差异。array_diff_key,如果有差异,则抛出异常。(防止什么?有啥作用)
这个类的其他方法
都是简单的返回该类的成员变量,或者进行一些加工,json_decode,array_map(‘trim’, explode(‘,’, $this->values[‘controllers’]));
Request.php
构造函数有很多参数,如果没有,则进入默认处理。即从$_SERVER数组中获取信息。
构造函数,步骤:
- parseMethod。默认参数method,设置成员变量的method值,优先级:参数 > $_SERVER[‘REQUEST_METHOD’] > 默认 ‘GET’ 。能否抽象成一个函数?
- parsePath。跟上一步差不多。设置成员变量path的值。优先级:参数 > $_SERVER[‘PATH_INFO’] > 默认 ‘/‘ 。另外,$this->pathSegments = explode(‘/‘, $path);
- parseParams。解析参数。也一样。设置成员变量 的值。$query 优先级:参数query > $_SERVER[‘QUERY_STRING’] > 默认 ‘’ 。
备注:test.php?id=1&b=c&a=1#jk id=1&b=c&a=1
$query = str_replace('][]=', ']=', str_replace('=', '[]=', $query));
//输出:"id=1&b=c&a=1&a=2" => id[]=1&b[]=c&a[]=1&a[]=2
//调用了php中系统方法,parse_str
parse_str($query, $this->params);
//作用:将形成 key => array() 形式,如果有多个,数组里面的内容则有多个。
- parseHeaders。如果没有传参数,那么从$_SERVER中以HTTP_开头的获取。。最终,$this->headers = $headers;
- parseBody。这个函数如下,下面这个曾经是我的遇到的一个问题。
private function parseBody(String $body = null) /*: void*/
{
if (!$body) {
$body = file_get_contents('php://input');
}
$this->body = $this->decodeBody($body);
}
- 复制成员变量highPerformance的值。 这个变量的作用,大概是阻止从$_SERVER中解析。
在这个类中,看到了很多注释掉的返回值类型。/: ?object/ 为何?难道是因为太严格了,报错? 另外,其他的方法,都是获取、设置成员变量的值。但是有个方法,fromString。
Response.php
输出结果的方法。总代码也不多。重点函数如下:
public function __construct(int $status, $body)
{
$this->status = $status;
$this->headers = array();
$this->parseBody($body);//这个步骤中进行了json_encode
}
public function output()
{
http_response_code($this->getStatus());
foreach ($this->headers as $key => $value) {
header("$key: $value");//之前一直听说双引号效率不高。但是这个却用了。
//我自己简单的测试了下,好像并没有太大的区别。
}
echo $this->getBody();
}
由于是api接口,所以如果data有值,则指定了Header(‘Content-Type’, ‘application/json’)。
Api.php
最复杂的应该是这个类,它本身不复杂,但是引入了很多的其他文件。相当于入口引导。从index.php的使用方法上可以看到,这个类很重要的一个方法是handle,然后返回一个response对象。
这个函数,主要用了api同目录下的其他子文件夹下面的文件。
这个类主要有三个私有成员变量。$router;$responder;$debug; 其中debug从配置中获取,然后控制添加调试的头信息。
这个类主要有两个方法,一个构造方法,一个handle方法。
- handle方法。从$response = $this->router->route($request);进行获取。如果异常,则try catch处理异常。异常的抛出的值,数据库抛出,ErrorCode中定义。
- 构造方法。这个是重点掌握。
SimpleRouter.php
这个是响应的核心。在api中,也是通过实例化此对象,然后调用route方法,会自动生成reponse对象。这个里面有个重点:$obj->handle($request)。其实,所有的中间件,包括SimpleRouter形成了一个单向的链,然后按栈的顺序加载。
public function route(Request $request): Response
{
if ($this->registration) {
$data = gzcompress(json_encode($this->routes, JSON_UNESCAPED_UNICODE));
$this->cache->set('PathTree', $data, $this->ttl);
}
$obj = $this;
if (count($this->middlewares) > 0) {
$obj = $this->middlewares[0];
}
return $obj->handle($request);//这个过程,在不出错的情况下,会依次调用所有的中间件,以及SimpleRouter
}
另外,load是用来将所有的中间件,加载成单链结构。
public function load(Middleware $middleware) /*: void*/
{
//从中间件队列前面,取一个元素作为$next。如果没有,则为当前路由。
if (count($this->middlewares) > 0) {
$next = $this->middlewares[0];
} else {
$next = $this;
}
$middleware->setNext($next);
//将新增的中间件,放在首位。
array_unshift($this->middlewares, $middleware);
}
上面的方法,在中间件对象生成的时候,又父类的Middleware自动执行
public function __construct(Router $router, Responder $responder, array $properties)
{
$router->load($this);
$this->responder = $responder;
$this->properties = $properties;
}
最终,中间件执行的顺序,类似栈结构。路由的hanlder,最后执行。
为了保存栈结构能顺利执行,所有的中间件在没有出错的情况下,都会执行,下语句
return $this->next->handle($request);