“播下一种思想,收获一种行为;播下一种行为,收获一种习惯;播下一种习惯,收获一种性格;播下一种性格,收获一种命运。” --《成君忆:水煮三国》
1.12.1 参数解析
参数,对于接口来说,是非常重要的输入。对于外部调用来说,同等重要。
因此,对于参数这块,我们是希望能够既减轻后台开发对接口参数获取、判断、验证、文档编写的痛苦;又便于客户端方便的、自由的调用;既利已又利他。
由此,我们引入了 参数解析 这一概念,即:通过配置参数的规则,即可自动实现参数的获取和验证。
1.12.2 参数解析的配置规则
熟悉Yii的同学,对于以下的规则配置应该倍感亲切,但是不熟悉的同学也可以同样快速上手。因为,你会慢慢发现,这样的规则很符合我们PHP开发的规范,如果没有,我们继续努力改进。
格式如下:
array(
'参数名' => array('name' => '接口参数名称', 'type' => '类型', 'default' => '默认值', ...),
... ...
)
1.12.3 示例
(1)简单的示例
假设这样的业务场景,我们需要提供一个用户登录的接口,其中需要用户名和密码,因此:
class Api_User extends Core_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);
}
}
当我们这样调用接口时:
/?service=User.Login&username=test&password=123456
就可以获取到需要的参数:
{"ret":0,"data":{"username":"test","password":"123456"},"msg":""}
从中,可以很容易理解:参数规则需要统一配置在接口实现类里面的 getRules() 函数,随后即可以通过类成员属性方式获取,如: $this->username 。
(2)更完善的示例
很多时候我们都会对用户名和密码作一些验证,如是否必须、长度、最值,以及默认值等。
继续上面的业务场景,我们登录下用户名和密码必须,且密码长度至少为6个字符,则可以调整参数规则:
'login' => array(
'username' => array('name' => 'username', 'require' => true),
'password' => array('name' => 'password', 'require' => true, 'min' => 6),
),
尝试一下非法的参数请求,如无任何参数的情况下,访问/?service=User.Login,返回:
{"ret":1200,"data":[],"msg":"Illegal Param: wrong param: username"}
再尝试一下密码长不对的情况,访问/?service=User.Login&username=test&password=123,返回:
{"ret":1200,"data":[],"msg":"Illegal Param: password.len should >= 6, but now password.len = 3"}
1.12.4 三级参数
(1)系统参数
已被系统固定占有的参数,目前只有一个,即:service,为需要调用的服务,类型为字符串,格式为:XXX.XXX,首字母不区分大小写,建议统一以大写开头。
(2)应用参数
应用参数是指在一个项目中,全部接口都需要的参数,或者通用的参数规则。假如我们的项目中全部需要签名sign参数,且必须;以及非必须的版本号,则可以在./Config/app.php中的apiCommonRules配置:
//$vim ./Config/app.php
return array(
/**
* 应用接口层的统一参数
*/
'apiCommonRules' => array(
//签名
'sign' => array(
'name' => 'sign', 'require' => true,
),
//客户端App版本号,如:1.0.1
'version' => array(
'name' => 'version', 'default' => '',
),
),
... ...
(3)接口参数
接口参数即为上面在各个接口子类中配置的规则,为特定接口所持有。同时,为了方便同一套接口的规则重用,可以使用下标为 '*' 表示是本接口通用规则,如我们为了加强安全性,为全部的用户接口操作都加上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),
),
);
}
在完成对上面的应用参数规则、接口通用规则和指定规则的参数进行配置后,对用户登录的接口进行请求时就需要这样访问:
/?service=User.login&sign=77f81c17d512302383e5f26b99dae4d9&username=test&password=123456&code=abcd
温馨提示: 在Api类里面配置规则时,下标不区分大小写。因为框架会自动将请求的函数名和全部的规则下标转换成小写进行匹配。
另外,当同一个参数规则分别在应用参数、接口通用参数及特定接口参数出现时,后面的规则会覆盖前面的,即具体化的规则和替换通用的规则,以保证接口在特定场合的定制性。
1.12.5 在线接口参数查询工具
为了便于理解上面全部的参数规则,对于具体接口调用的要求,这里可以使用在线接口参数查询工具在浏览器访问查看:
/demo/checkApiParams.php?service=User.Login
可以看到:
此工具同时也可以方便客户端实时查看接口文档时,进行辅助的接口规则说明。
1.12.6 参数传递的方式
系统下GET和POST皆可,但是推荐:
1、service参数以GET方式传递,接口统一以/?service=XXX.XXX链接请求,便于交流,更重要的是当接口发生问题时,可以快速在服务器上通过nginx日记定位问题;
2、其他参数以POST方式传递,特别对于敏感数据,如密码,以相对保护数据安全;
3、在编写文档,或者进行调试时,可以全部临时使用GET方式,如本文档的写法,同时在浏览器时也可以使用GET;
4、如果需要对数据包进行加密或者压缩、自定义参数格式,可以重载PhalApi_Request::genData(),然后再继续使用参数规则解析;
1.12.7 参数规则
类型type
参数名称 name
是否必须require
默认值default
最小值&最大值min&max
更多
string
必须
可选
true/false,默认false
可选
---
int
必须
可选
true/false,默认false
可选
---
float
必须
可选
true/false,默认false
可选
---
boolean
必须
可选
true/false,默认false
可选
以下值会转换为true: ok, true, success, on, yes, 1
date
必须
可选
true/false,默认false
可选
格式:format 为timestamp时会将字符串的日期转换
array
必须
可选
true/false,默认false
可选
格式:format 为explode时,会根据separator将字符串分割成数组, 为json时,会json解析
enum
必须
可选
true/false,默认false
可选
必须,range,以数组指定枚举的范围
下面是对各类型的示例说明。
(1)字符串 string
当一个参数规则 未指定类型时,默认为string。一个完整的写法可以为:
array('name' => 'username', 'type' => 'string', 'require' => true, 'default' => 'nobody', 'min' => 1, 'max' => 10)
若传递的参数长度过长,如&username=alonglonglonglongname,则会异常失败返回:
{"ret":1200,"data":[],"msg":"Illegal Param: username.len should <= 10, but now username.len = 21"}
(2)整型 int
如通常数据库中的id,即可配置成:
array('name' => 'id', 'type' => 'int', 'require' => true, 'min' => 1 )
当传递的参数,不在其配置的范围内时,如&id=0,则会异常失败返回:
{"ret":1200,"data":[],"msg":"Illegal Param: id should >= 1, but now id = 0"}
(3)浮点 float
浮点型,类似整型的配置,此处略。
(4)布尔值 boolean
布尔值,主要是可以对一些字符串转换成布尔值,如ok, true, success, on, yes, 以及会被PHP解析成true的字符串,都会转换成true,方便调用。如通常的是否记住我:
array('name' => 'isRememberMe', 'type' => 'boolean', 'default' => true)
(5)日期 date
日期可以按自己约定的格式传递,当需要将字符串的日期转换成timestamp时,可以这样配置:
array('name' => 'registerData', 'type' => 'date')
对应®isterData=2015-01-31%2010:00:00则会被获取到为:"2015-01-31 10:00:00"。
如果是配置成:
array('name' => 'registerData', 'type' => 'date', 'format' => 'timestamp')
则上面的参数再请求时,则会被转换成:1422669600。
(6)数组 array
很多时候在接口进行批量获取时,都需要提供一组参数,所以这时可以使用数组来进行配置。如:
array('name' => 'uids', 'type' => 'array', 'format' => 'explode', 'separator' => ',')
对应&uids=1,2,3则会被转换成:
array ( 0 => '1', 1 => '2', 2 => '3', )
又如接口需要使用JSON来传递整块参数时,可以这样配置:
array('name' => 'params', 'type' => 'array', 'format' => 'json')
对应¶ms={"username":"test","password":"123456"}则会被转换成:
array ( 'username' => 'test', 'password' => '123456', )
特别地,当配置成了数组,却未指定格式format时,会转换成一个元素的数组,如:&name=test,会转换成:array('test')。
(7)枚举 enum
在需要对接口参数进行范围限制时,可以使用此枚举型。如对于性别的参数,可以这样配置:
array('name' => 'sex', 'type' => 'enum', 'range' => array('female', 'male'))
当传递的参数不合法时,如&sex=unknow,则会被拦截,返回失败:
{"ret":1200,"data":[],"msg":"Illegal Param: sex should be in female\/male, but now sex = unknow"}
1.12.8 关于参数设计的原则
(1)通配的$_REQUEST
使用$_REQUEST获取参数,便于在不同场合下GET/POST之间的切换,同时在初始化DI()->request服务时,可以指定传递的参数,以便于灵活的单元测试;
(2)更自由的名称映射
之所以没把规则配置的下标默认成与客户端传递的name一致,是为了更自由的名称映射;
如可能我们PHP后台开发喜欢用驼峰法来表示,但客户端想用下划线来分割,则通过这样配置:
array(
'isRememberMe' => array('name' => 'is_remember_me', 'type' => 'boolean', 'default' => true),
)
更重要的是,有时我们希望能缩短客户端请求的参数名称以节省流量时,可以这样配置:
array(
'isRememberMe' => array('name' => 're', 'type' => 'boolean', 'default' => true),
)
(3)异常返回
对于客户端参数不合法时,以异常失败返回,而不是隐性地转换,是因为后台接口往往需要手动对传递的参数进行人工的验证,而不是希望得到隐性转换的值。即当客户端参数对时,我们需要明确提示说:参数非法。