Api接口层称为接口服务层,负责对客户端的请求进行响应,处理接收客户端传递的参数,进行高层决策并对领域业务层进行调度,最后将处理结果返回给客户端。
接口参数规则配置
接口参数,对于接口服务本身来说,是非常重要的。对于外部调用的客户端来说,同等重要。对于接口参数,我们希望能够既减轻后台开发对接口参数获取、判断、验证、文档编写的痛苦;又能方便客户端快速调用,明确参数的意义。由此,我们引入了参数规则这一概念,即:通过配置参数的规则,自动实现对参数的获取和验证,同时自动生成在线接口文档。
一个简单的示例
假设我们现在需要提供一个用户登录的接口,接口参数有用户名和密码,那么新增的接口类和规则如下:
// 文件 ./src/app/Api/User.php
<?php
namespace App\Api;
use PhalApi\Api;
class User extends Api {
public function getRules() {
return array(
'login' => array(
'username' => array('name' => 'username'),
'password' => array('name' => 'password'),
),
);
}
public function login() {
return array('username' => $this->username, 'password' => $this->password);
}
}
当请求此接口服务,并类似这样带上username和password参数时:
/?s=User.Login&username=dogstar&password=123456
就可以得到这样的返回结果。
{"ret":0,"data":{"username":"dogstar","password":"123456"},"msg":""}
参数规则格式
参数规则是针对各个接口服务而配置的多维规则数组,由接口类的getRules()
方法返回。其中,
- 一维下标是接口类的方法名,对应接口服务的Action;
- 二维下标是类属性名称,对应在服务端获取通过验证和转换化的最终客户端参数;
- 三维下标name是接口参数名称,对应外部客户端请求时需要提供的参数名称。
小结如下:
public function getRules() {
return array(
'接口类方法名' => array(
'接口类属性' => array('name' => '接口参数名称', ... ... ),
),
);
}
在接口实现类里面getRules()
成员方法配置参数规则后,便可以通过类属性的方式,根据配置指定的名称获取对应的接口参数,如上面的:$this->username
和$this->password
。
三级参数规则配置
参数规则主要有三种,分别是:系统参数规则、应用参数规则、接口参数规则。
系统参数
系统参数是指被框架保留使用的参数。目前已被PhalApi占用的系统参数只有一个,即:service参数(缩写为s参数),前面已有介绍。
应用参数
应用参数是指在一个接口系统中,全部项目的全部接口都需要的参数,或者通用的参数。假如我们的商城接口系统中全部的接口服务都需要必须的签名sign参数,以及非必须的版本号,则可以在./config/app.php
中的apiCommonRules
进行应用参数规则的配置:
<?php
return array(
/**
* 应用接口层的统一参数
*/
'apiCommonRules' => array(
//签名
'sign' => array(
'name' => 'sign', 'require' => true,
),
//客户端App版本号,默认为:1.4.0
'version' => array(
'name' => 'version', 'default' => '1.4.0',
),
),
)
接口参数
接口参数是指各个具体的接口服务所需要的参数,为特定的接口服务所持有,独立配置。并且进一步在内部又细分为两种:
- 通用接口参数规则:使用*作为下标,对当前接口类全部的方法有效。
- 指定接口参数规则:使用方法名作为下标,只对接口类的特定某个方法有效。
例如为了加强安全性,需要为全部的用户接口服务都加上长度为4位的验证码参数:
public function getRules() {
return array(
'*' => array(
'code' => array('name' => 'code', 'require' => true, 'min' => 4, 'max' => 4),
),
'login' => array(
'username' => array('name' => 'username', 'require' => true),
'password' => array('name' => 'password', 'require' => true, 'min' => 6),
),
);
}
现在,当再次请求用户登录接口,除了要提供用户名和密码外,我们还要提供验证码code参数。并且,对于Api\User类的其他方法也一样。
多个参数规则时的优先级
当同一个参数规则分别在应用参数、通用接口参数及指定接口参数出现时,后面的规则会覆盖前面的,即具体化的规则会替换通用的规则,以保证接口参数满足特定场合的定制要求。
简而言之,多个参数规则的优先级从高到下,分别是(正如你想到的那样):
- 1、指定接口参数规则
- 2、通用接口参数规则
- 3、应用参数规则
- 4、系统参数规则
温馨提示:如果对过滤器配置了白名单,必选参数最终会自动切换为可选参数,即require = false
,详细请参考白名单配置。
参数规则配置详细说明
具体的参数规则,根据不同的类型有不同的配置选项,以及一些公共的配置选项。目前,主要的类型有:字符串、整数、浮点数、布尔值、时间戳/日期、数组、枚举类型、文件上传和回调函数。
类型 type | 参数名称 name | 是否必须 require | 默认值 default | 最小值 min,最大值 max | 更多配置选项(无特殊说明,均为可选) |
---|---|---|---|---|---|
字符串 | string | TRUE/FALSE,默认FALSE | 应为字符串 | 可选 | regex选项用于配置正则匹配的规则;format选项用于定义字符编码的类型,如utf8、gbk、gb2312等 |
整数 | int | TRUE/FALSE,默认FALSE | 应为整数 | 可选 | —- |
浮点数 | float | TRUE/FALSE,默认FALSE | 应为浮点数 | 可选 | —- |
布尔值 | boolean | TRUE/FALSE,默认FALSE | true/false | —- | 以下值会转换为TRUE:ok,true,success,on,yes,1,以及其他PHP作为TRUE的值 |
时间戳/日期 | date | TRUE/FALSE,默认FALSE | 日期字符串 | 可选,仅当为format配置为timestamp时才判断,且最值应为时间戳 | format选项用于配置格式,为timestamp时会将字符串的日期转换为时间戳 |
数组 | array | TRUE/FALSE,默认FALSE | 字符串或者数组,为非数组会自动转换/解析成数组 | 可选,判断数组元素个数 | format选项用于配置数组和格式,为explode时根据separator选项将字符串分割成数组, 为json时进行JSON解析 |
枚举 | enum | TRUE/FALSE,默认FALSE | 应为range选项中的某个元素 | —- | 必须的range选项,为一数组,用于指定枚举的集合 |
文件 | file | TRUE/FALSE,默认FALSE | 数组类型 | 可选,用于表示文件大小范围,单位为B | range选项用于指定可允许上传的文件类型;ext选项用于表示需要过滤的文件扩展名 |
回调 | callable/callback | TRUE/FALSE,默认FALSE | —- | —- | callable/callback选项用于设置回调函数,params选项为回调函数的第三个参数(另外第一个为参数值,第二个为所配置的规则) |
公共配置选项
公共的配置选项,除了上面的类型、参数名称、是否必须、默认值,还有说明描述、数据来源。下面分别简单说明。
类型 type用于指定参数的类型,可以是string、int、float、boolean、date、array、enum、file、callable,或者自定义的类型。未指定时,默认为字符串。
参数名称 name接口参数名称,即客户端需要传递的参数名称。与PHP变量规则一样,以下划线或字母开头。此选项必须提供,否则会提示错误。
是否必须require为TRUE时,表示此参数为必须值;为FALSE时,表示此参数为可选。未指定时,默认为FALSE。
默认值 default未提供接口参数时的默认值。未指定时,默认为NULL。
最小值 min,最大值 max部分类型适用。用于指定接口参数的范围,比较时采用的是闭区间,即范围应该为:[min, max]。也可以只使用min或max,若只配置了min,则表示:[min, +∞);若只配置了maz,则表示:(-∞, max]。
说明描述 desc用于自动生成在线接口详情文档,对参数的含义和要求进行扼要说明。未指定时,默认为空字符串。
数据来源 source指定当前单个参数的数据来源,可以是post、get、cookie、server、request、header、或其他自定义来源。未指定时,默认为统一数据源。目前支持的source与对应的数据源映射关系如下:
source | 对应的数据源 |
---|---|
post | $_POST |
get | $_GET |
cookie | $_COOKIE |
server | $_SERVER |
request | $_REQUEST |
header | $_SERVER['HTTP_X'] |
通过source参数可以轻松、更自由获取不同来源的参数。以下是一些常用的配置示例。
// 获取HTTP请求方法,判断是POST还是GET
'method' => array('name' => 'REQUEST_METHOD', 'source' => 'server'),
// 获取COOKIE中的标识
'is_new_user' => array('name' => 'is_new_user', 'source' => 'cookie'),
// 获取HTTP头部中的编码,判断是否为utf-8
'charset' => array('name' => 'Accept-Charset', 'source' => 'header'),
若配置的source为无效或非法时,则会抛出异常。如配置了'source' => 'NOT_FOUND'
,会得到:
"msg": "服务器运行错误: 参数规则中未知的数据源:NOT_FOUND"
9种参数类型
对于各种参数类型,结合示例说明如下。
- 字符串 string
当一个参数规则未指定类型时,默认为string。如最简单的:
array('name' => 'username')
温馨提示:这一小节的参数规则配置示例,都省略了类属性,以关注配置本身的内容。
这样就配置了一个参数规则,接口参数名字叫username,类型为字符串。
一个完整的写法可以为:
array('name' => 'username', 'type' => 'string', 'require' => true, 'default' => 'nobody', 'min' => 1, 'max' => 10)
这里指定了为必选参数,默认值为nobody,且最小长度为1个字符,最大长度为10个字符,若传递的参数长度过长,如&username=alonglonglonglongname
,则会异常失败返回:
"msg": "非法请求:username.len应该小于等于10, 但现在username.len = 21"
当需要验证的是中文的话,由于一个中文字符会占用3个字节。所以在min和max验证的时候会出现一些问题。为此,PhalApi提供了format配置选项,用于指定字符集。如:
array('name' => 'username', 'type' => 'string', 'format' => 'utf8', 'min' => 1, 'max' => 10)
我们还可以使用regex
下标来进行正则表达式的验证,一个邮箱的例子是:
array('name' => 'email', 'regex' => "/^([0-9A-Za-z\\-_\\.]+)@([0-9a-z]+\\.[a-z]{2,3}(\\.[a-z]{2})?)$/i")
- 整型 int
整型即自然数,包括正数、0和负数。如通常数据库中的id,即可配置成:
array('name' => 'id', 'type' => 'int', 'require' => true, 'min' => 1)
当传递的参数,不在其配置的范围内时,如&id=0
,则会异常失败返回:
"msg": "非法请求:id应该大于或等于1, 但现在id = 0"
另外,对于常见的分页参数,可以这样配置:
array('name' => 'page_num', 'type' => 'int', 'min' => 1, 'max' => 20, 'default' => 20)
即每页数量最小1个,最大20个,默认20个。
浮点 float
浮点型,类似整型的配置,此处略。布尔值 boolean
布尔值,主要是可以对一些字符串转换成布尔值,如ok,true,success,on,yes,以及会被PHP解析成true的字符串,都会转换成TRUE。如通常的“是否记住我”参数,可配置成:
array('name' => 'is_remember_me', 'type' => 'boolean', 'default' => TRUE)
则以下参数,最终服务端会作为TRUE接收。
?is_remember_me=ok
?is_remember_me=true
?is_remember_me=success
?is_remember_me=on
?is_remember_me=yes
?is_remember_me=1
- 日期 date
日期可以按自己约定的格式传递,默认是作为字符串,此时不支持范围检测。例如配置注册时间:
array('name' => 'register_date', 'type' => 'date')
对应地,register_date=2015-01-31 10:00:00
则会被获取到为:"2015-01-31 1000"。
当需要将字符串的日期转换成时间戳时,可追加配置选项'format' => 'timestamp'
,则配置成:
array('name' => 'register_date', 'type' => 'date', 'format' => 'timestamp')
则上面的参数再请求时,则会被转换成:1422669600。
此时作为时间戳,还可以添加范围检测,如限制时间范围在31号当天:
array('name' => 'register_date', 'type' => 'date', 'format' => 'timestamp', 'min' => 1422633600, 'max' => 1422719999)
当配置的最小值或最大值为字符串的日期时,会自动先转换成时间戳再进行检测比较。如可以配置成:
array('name' => 'register_date', ... ... 'min' => '2015-01-31 00:00:00', 'max' => '2015-01-31 23:59:59')
- 数组 array
很多时候在接口进行批量获取时,都需要提供一组参数,如多个ID,多个选项。这时可以使用数组来进行配置。如:
array('name' => 'uids', 'type' => 'array', 'format' => 'explode', 'separator' => ',')
这时接口参数&uids=1,2,3
则会被转换成:
array ( 0 => '1', 1 => '2', 2 => '3', )
如果设置了默认值,那么默认值会从字符串,根据相应的format格式进行自动转换。如:
array( ... ... 'default' => '4,5,6')
那么在未传参数的情况下,自动会得到:
array ( 0 => '4', 1 => '5', 2 => '6', )
又如接口需要使用JSON来传递整块参数时,可以这样配置:
array('name' => 'params', 'type' => 'array', 'format' => 'json')
对应地,接口参数¶ms={"username":"test","password":"123456"}
则会被转换成:
array ( 'username' => 'test', 'password' => '123456', )
温馨提示:使用JSON传递参数时,建议使用POST方式传递。若使用GET方式,须注意参数长度不应超过浏览器最大限制长度,以及URL编码问。
若使用JSON格式时,设置了默认值为:
array( ... ... 'default' => '{"username":"dogstar","password":"xxxxxx"}')
那么在未传参数的情况下,会得到转换后的:
array ( 'username' => 'dogstar', 'password' => 'xxxxxx', )
特别地,当配置成了数组却未指定格式format时,接口参数会转换成只有一个元素的数组,如接口参数:&name=test
,会转换成:
array ( 0 => 'test' )
- 枚举 enum
在需要对接口参数进行范围限制时,可以使用此枚举型。如对于性别的参数,可以这样配置:
array('name' => 'sex', 'type' => 'enum', 'range' => array('female', 'male'))
当传递的参数不合法时,如&sex=unknow
,则会被拦截,返回失败:
"msg": "非法请求:参数sex应该为:female/male,但现在sex = unknow"
关于枚举类型的配置,这里需要特别注意配置时,应尽量使用字符串的值。 因为通常而言,接口通过GET/POST方式获取到的参数都是字符串的,而如果配置规则时指定范围用了整型,会导致底层规则验证时误判。例如接口参数为&type=N
,而接口参数规则为:
array('name' => 'type', 'type' => 'enum', 'range' => array(0, 1, 2))
则会出现以下这样的误判:
var_dump(in_array('N', array(0, 1, 2))); // 结果为true,因为 'N' == 0
为了避免这类情况发生,应该使用使用字符串配置范围值,即可这样配置:
array('name' => 'type', 'type' => 'enum', 'range' => array(`0`, `1`, `2`))
- 文件 file
在需要对上传的文件进行过滤、接收和处理时,可以使用文件类型,如:
array(
'name' => 'upfile',
'type' => 'file',
'min' => 0,
'max' => 1024 * 1024,
'range' => array('image/jpeg', 'image/png') ,
'ext' => array('jpeg', 'png')
)
其中,min和max分别对应文件大小的范围,单位为字节;range为允许的文件类型,使用数组配置,且不区分大小写。
如果成功,返回的值对应的是$_FILES["upfile"]
,即会返回:
array(
'name' => ..., // 被上传文件的名称
'type' => ..., // 被上传文件的类型
'size' => ..., // 被上传文件的大小,以字节计
'tmp_name' => ..., // 存储在服务器的文件的临时副本的名称
)
对应的是:
- $_FILES["upfile"]["name"] - 被上传文件的名称
- $_FILES["upfile"]["type"] - 被上传文件的类型
- $_FILES["upfile"]["size"] - 被上传文件的大小,以字节计
- $_FILES["upfile"]["tmp_name"] - 存储在服务器的文件的临时副本的名称
- $_FILES["upfile"]["error"] - 由文件上传导致的错误代码
参考:以上内容来自W3School,文件上传时请使用表单上传,并enctype 属性使用"multipart/form-data"。更多请参考PHP 文件上传。
若需要配置默认值default选项,则也应为一数组,且其格式应类似如上。
其中,ext是对文件后缀名进行验证,当如果上传文件后缀名不匹配时将抛出异常。文件扩展名的过滤可以类似这样进行配置:
- 单个后缀名 - 数组形式
'ext' => array('jpg')
- 单个后缀名 - 字符串形式
'ext' => 'jpg'
- 多个后缀名 - 数组形式
'ext' => array('jpg', 'jpeg', 'png', 'bmp')
- 多个后缀名 - 字符串形式(以英文逗号分割)
'ext' => 'jpg,jpeg,png,bmp'
- 回调 callable/callback
当需要利用已有函数进行自定义验证时,可采用回调参数规则,如配置规则:
array('name' => 'version', 'type' => 'callable', 'callback' => 'App\\Common\\Request\\Version::formatVersion')
然后,回调时将调用下面这个新增的类函数:
<?php
namespace App\Common\Request;
use PhalApi\Exception\BadRequestException;
class Version {
public static function formatVersion($value, $rule) {
if (count(explode('.', $value)) < 3) {
throw new BadRequestException('版本号格式错误');
}
return $value;
}
}
回调函数的签名为:function format($value, $rule, $params)
,第一个为参数原始值,第二个为所配置的规则,第三个可选参数为配置规则中的params选项。最后应返回转换后的参数值。
接口返回
回顾一下,在PhalApi中,掊口返回的结果的结构为:
{
"ret": 200, // 状态码
"data": {
// 业务数据
},
"msg": "" // 错误提示信息
}
正常情况下的返回
正常情况下,在Api层返回的数据结果,会在返回结果的data字段中体现。例如:
class Hello extends Api {
public function world() {
return array('title' => 'Hello World!');
}
}
对应:
{
"ret": 200,
"data": {
"title": "Hello World!"
},
"msg": ""
}
成功返回时,状态码ret为200,并且错误信息msg为空。
失败情况下的返回
对于异常情况,包括系统错误或者应用层的错误,可以通过抛出PhalApi\Exception系列的异常,中断请求并返回相关的错误信息。例如:
class Hello extends Api {
public function fail() {
throw new BadRequestException('签名失败', 1);
}
}
会得到以下结果输出:
{
"ret": 401,
"data": [],
"msg": "Bad Request: 签名失败"
}
返回JSONP、XML等其他格式
JSONP返回格式
如果需要支持JSONP返回格式,可以将 ./config/di.php
中的以下代码注释去掉:
// 支持JsonP的返回
if (!empty($_GET['callback'])) {
$di->response = new \PhalApi\Response\JsonpResponse($_GET['callback']);
}
然后在请求时,传入回调函数的名称callback,即可返回JSONP格式。例如请求:
http://dev.phalapi.net/?s=Hello.World&callback=test
返回:
test({"ret":200,"data":{"title":"Hello World!"},"msg":""})
XML返回格式
如果需要返回XML格式,需要将\PhalApi\DI()->response
切换到XML响应类,如:
$di->response = new \PhalApi\Response\XmlResponse();
然后,可看到请求的接口返回类似如下:
<?xml version="1.0" encoding="utf-8"?><xml><ret><![CDATA[200]]></ret><data><title><![CDATA[Hello World!]]></title></data><msg><![CDATA[]]></msg></xml>
其他返回格式
常用的返回格式有如上的JSON、JSONP、XML返回格式。如果需要返回其他的格式,你可以:
- 1、实现\PhalApi\Response抽象中的formatResult($result)格式化返回结果
- 2、重新注册\PhalApi\DI()->response服务
如果希望能由客户端指定返回格式,可通过参数来动态切换。
注释与在线文档
PhalApi提供了自动生成的在线接口文档,对于每一个接口服务,都有对应的在线接口详情文档。如默认接口服务Site.Index
的在线接口详情文档为:
此在线接口详情文档,从上到下,依次说明如下。
接口服务名称
接口服务名称是指用于请求时的名称,对应s参数(或service参数)。接口服务的中文名称,为不带任何注解的注释,通常为接口类成员函数的第一行注释。
class Site extends Api {
/**
* 默认接口服务
*/
public function index() {
}
}
接口说明
接口说明对应接口类成员函数的@desc
注释。
class Site extends Api {
/**
* 默认接口服务
* @desc 默认接口服务,当未指定接口服务时执行此接口服务
*/
public function index() {
}
}
接口参数
接口参数是根据接口类配置的参数规则自动生成,即对应当前接口类getRules()
方法中的返回。其中最后的“说明” 字段对应参数规则中的desc选项。可以配置多个参数规则。此外,配置文件./config/app.php中的公共参数规则也会显示在此接口参数里。
class Site extends Api {
public function getRules() {
return array(
'index' => array(
'username' => array('name' => 'username', 'default' => 'PHPer', ),
),
);
}
}
返回结果
返回结果对应接口类成员函数的@return
注释,可以有多组,格式为:@return 返回类型 返回字段 说明
。
class Site extends Api {
/**
* 默认接口服务
* @desc 默认接口服务,当未指定接口服务时执行此接口服务
* @return string title 标题
* @return string content 内容
* @return string version 版本,格式:X.X.X
* @return int time 当前时间戳
*/
public function index() {
}
}
异常情况
异常情况对应@exception
注释,可以有多组,格式为:@exception 错误码 错误描述信息
。例如:
/**
* @exception 406 签名失败
*/
public function index() {
刷新后,可以看到新增的异常情况说明。
公共注释
对于当前类的全部函数成员的公共@exception
异常情况注释和@return
返回结果注释,可在类注释中统一放置。而对于多个类公共的@exception和
@return```注释,则可以在父类的类注释中统一放置。
也就是说,通过把@exception
注解和@return
注解移到类注释,可以添加全部函数成员都适用的注解。例如,Api\User类的全部接口都返回code字段,且都返回400和500异常,则可以:
<?php
namespace App\Api;
use PhalApi\Api;
/**
* @return int code 操作码,0表示成功
* @exception 400 参数传递错误
* @exception 500 服务器内部错误
*/
class User extends Api {
这样就不需要在每个函数成员的注释中重复添加注解。此外,也可以在父类的注释中进行添加。对于相同异常码的@exception
注解,子类的注释会覆盖父类的注释,方法的注释会覆盖类的注释;而对于相同的返回结果@return
注释,也一样。
需要注意的是,注释必须是紧挨在类的前面,而不能是在namespace前面,否则会导致注释解析失败。