PHP接口设计之道

设计前后端完全分离的应用,可以使用到RESTful API。比如, Backbone 的默认规则最适合的是一个完全 restful 风格的后端接口,如果你的后端系统没有准备好,那就直接覆盖掉吧。restful 不光是前端的事~ 是构架层面上的事情,如果想用Backbone的话,肯定是需要后端重新定义所有的接口了,但是这也是好事,毕竟 restful 逻辑更清晰,以后的更新,维护会更方便。

一、基础

现在的网站没有API都给人落后的印象了,而当下设计API流行的规范便是RESTful。REST全称是Representational State Transfer,中文意思是表述(编者注:通常译为表征)性状态转移。它首次出现在2000年Roy Fielding的博士论文中,Roy Fielding是HTTP规范的主要编写者之一。 他在论文中提到:”我这篇文章的写作目的,就是想在符合架构原理的前提下,理解和评估以网络为基础的应用软件的架构设计,得到一个功能强、性能好、适宜通信的架构。REST指的是一组架构约束条件和原则。” 如果一个架构符合REST的约束条件和原则,我们就称它为RESTful架构。

它涉及到以下这些内容。

1.1 域名

应尽量部署在专用域名之下,可以在前边加个二级头或者域名后添加api路径。

https://api.example.com
https://example.com/api/

1.2 版本

API经常会变,因此要注意区分版本。一般放入URL中。

https://api.example.com/v1/

1.3 路径

路径又称”终点”(endpoint),表示API的具体网址。
在RESTful架构中,每个网址代表一种资源(resource),所以网址中不能有动词,只能有名词,而且所用的名词往往与数据库的表格名对应。一般来说,数据库中的表都是同种记录的”集合”(collection),所以API中的名词也应该使用复数。
举例来说,有一个API提供动物园(zoo)的信息,还包括各种动物和雇员的信息,则它的路径应该设计成下面这样。

https://api.example.com/v1/zoos
https://api.example.com/v1/animals
https://api.example.com/v1/employees

1.4 HTTP动词

对于资源的具体操作类型,由HTTP动词表示。
常用的HTTP动词有下面五个(括号里是对应的SQL命令)。

  • GET(SELECT):从服务器取出资源(一项或多项)
  • POST(CREATE):在服务器新建一个资源
  • PUT(UPDATE):在服务器更新资源(客户端提供改变后的完整资源)
  • PATCH(UPDATE):在服务器更新资源(客户端提供改变的属性)
  • DELETE(DELETE):从服务器删除资源

下面是一些例子。

  • GET /zoos:列出所有动物园
  • POST /zoos:新建一个动物园
  • GET /zoos/ID:获取某个指定动物园的信息
  • PUT /zoos/ID:更新某个指定动物园的信息(提供该动物园的全部信息)
  • PATCH /zoos/ID:更新某个指定动物园的信息(提供该动物园的部分信息)
  • DELETE /zoos/ID:删除某个动物园
  • GET /zoos/ID/animals:列出某个指定动物园的所有动物
  • DELETE /zoos/ID/animals/ID:删除某个指定动物园的指定动物

1.5 过滤信息

如果记录数量很多,服务器不可能都将它们返回给用户。API应该提供参数,过滤返回结果。
下面是一些常见的参数。

  • ?limit=10:指定返回记录的数量
  • ?offset=10:指定返回记录的开始位置
  • ?page=2&per_page=100:指定第几页,以及每页的记录数
  • ?sortby=name&order=asc:指定返回结果按照哪个属性排序,以及排序顺序
  • ?animal_type_id=1:指定筛选条件

1.6 状态码

服务器向用户返回的状态码和提示信息,常见的有以下一些(方括号中是该状态码对应的HTTP动词)。

  • 200 OK – [GET]:服务器成功返回用户请求的数据,该操作是幂等的(Idempotent)
  • 201 CREATED – [POST/PUT/PATCH]:用户新建或修改数据成功
  • 202 Accepted – [*]:表示一个请求已经进入后台排队(异步任务)
  • 204 NO CONTENT – [DELETE]:用户删除数据成功
  • 400 INVALID REQUEST – [POST/PUT/PATCH]:用户发出的请求有错误,服务器没有进行新建或修改数据的操作,该操作是幂等的
  • 401 Unauthorized – [*]:表示用户没有权限(令牌、用户名、密码错误)
  • 403 Forbidden – [*] 表示用户得到授权(与401错误相对),但是访问是被禁止的
  • 404 NOT FOUND – [*]:用户发出的请求针对的是不存在的记录,服务器没有进行操作,该操作是幂等的
  • 405 method not allowed – [*]:该http方法不被允许
  • 406 Not Acceptable – [GET]:用户请求的格式不可得(比如用户请求JSON格式,但是只有XML格式)
  • 410 Gone -[GET]:用户请求的资源被永久删除,且不会再得到的
  • 422 Unprocesable entity – [POST/PUT/PATCH] 当创建一个对象时,发生一个验证错误
  • 500 INTERNAL SERVER ERROR – [*]:服务器发生错误,用户将无法判断发出的请求是否成功

1.7 错误处理

如果状态码是4xx,就应该向用户返回出错信息。一般来说,返回的信息中将error作为键名,出错信息作为键值即可。

1
{error: "invalid API key"}

1.8 返回结果

针对不同操作,服务器向用户返回的结果应该符合以下规范。

  • GET /collection:返回资源对象的列表(数组)
  • GET /collection/resource:返回单个资源对象
  • POST /collection:返回新生成的资源对象
  • PUT /collection/resource:返回完整的资源对象
  • PATCH /collection/resource:返回完整的资源对象
  • DELETE /collection/resource:返回一个空文档

二、最佳实践

2.1 Slim框架的使用

推荐一个轻量版PHP RESTful框架:Slim ,有兴趣的同学可以参考其文档:http://docs.slimframework.com/

下面以一个网上的教程给大家说明,主要是应用前端框架AngularJS 和 后端PHP框架Slim搭设的一个简易图书管理系统为例。

代码实现

主要的工作是在项目根目录下的index.php文件里进行的,第一步需要引入Slim框架

1
2
3
4
5
//第一步:引用Slim框架
//首先,需要在你的index.php中引入Slim框架的依赖,根据实际情况,你可能得调整下文件的路径
require 'Slim/Slim.php';
\Slim\Slim::registerAutoloader();

2.x 测试 - 插件使用

  1. Chrome 插件 Postman:
    Alt text

  2. Chrome插件Json-viewer 和 json-handle

###

三、扩展

3.1 不要默认使用大括号封装,但要在需要的时候支持

json 还是回调,那是个问题:

1
2
3
4
5
6
{
"data" : {
"id" : 123,
"name" : "John"
}
}

or

1
2
3
4
5
6
7
callback_function({
status_code: 200,
next_page: "https://..",
response: {
... actual JSON response body ...
}
})

好的做法是:

就返回对应结构的数据。

3.2 如何使用 Last-Modified 和 Etags 如何帮助提高性能?

开发者会把 Last-Modified 和 ETags 请求的 HTTP 报头一起使用,这样可利用客户端(例如浏览器)的缓存。因为服务器首先产生 Last-Modified/Etag 标记,服务器可在稍后使用它来判断页面是否已经被修改。本质上,客户端通过将该记号传回服务器要求服务器验证其缓存是否过期。

HTTP 协议规格说明定义 ETag 为“被请求变量的实体值”。 服务器单独负责判断记号是什么及其含义,并在 HTTP 响应头中将其传送到客户端,以下是服务器端返回的格式:

ETag: “d41d8cd98f00b204e9800998ecf8427e”

客户端的查询更新格式是这样的:

If-None-Match: W/“d41d8cd98f00b204e9800998ecf8427e”

如果ETag没改变,则返回状态304,内容为空,这也和Last-Modified一样。下面再扔些php的例子看看:

1
2
3
4
5
6
7
8
9
10
11
12
$file = 'myfile.php';
$last_modified_time = filemtime($file);
$etag = md5_file($file);
header("Last-Modified: ".gmdate("D, d M Y H:i:s", $last_modified_time)." GMT");
header("Etag: $etag");
if (@strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']) == $last_modified_time ||
trim($_SERVER['HTTP_IF_NONE_MATCH']) == $etag) {
header("HTTP/1.1 304 Not Modified");
exit;
}

补充:对于前端部分网上的一些做法是:Gulp对所有的静态资源进行预处理,生成一份manifest,标明了预处理前后文件之间的对应关系。DEMO, 另外腾讯前端团队的一篇关于web缓存系列文章大家也可以查阅下:直通车

总结

技术的变革一般都是为了提高生产力的,restful的初衷也是。它提倡简单(理论上讲只要业务实体抽象的好就够了),纯粹(每个资源就四种操作)的 API 设计思想,需要使用者坚持信仰(坚持基本原则),适度灵活。

— 来自SF.gg

Q&A

1. 务器返回的数据格式,应该使用JSON还是使用XML?

推送使用JSON,如果提供的服务对象较多,可以设置兼容XML

2. REST能应用在什么场景?

REST规范中明确规定,处理的是资源(或者实体),而不是动作。换句话说,REST处理的是books或者animals这种东西,而login之类的业务逻辑是动作,不适用REST。

restful 可以看做 orm 的 web api 形式,也就是说,资源指的是数据库里面的表(或者表的连接),或者是你nosql数据库里面的对象。当调用者需要并且有权直接操作它们,或者是以表的粒度操作数据库,才需要使用 restful。

3. 统计能使用REST吗?
4. 兴趣探讨Vue.js、ReactJS、AngularJS
5. 扩展:API Cookie加密
6. php crud 和resetful的相似之处

参考

简单版:

高阶版:


UPS: