文件源代码分析

写在前面

php-crud-api

这个是数据库的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);