_“播下一种思想,收获一种行为;播下一种行为,收获一种习惯;播下一种习惯,收获一种性格;播下一种性格,收获一种命运。” --《成君忆:水煮三国》_
##1.12.1 参数解析
参数,对于接口来说,是非常重要的输入。对于外部调用来说,同等重要。
因此,对于参数这块,我们是希望能够既减轻后台开发对接口参数获取、判断、验证、文档编写的痛苦;又便于客户端方便的、自由的调用;既利已又利他。
由此,我们引入了 **参数解析** 这一概念,即:通过配置参数的规则,即可自动实现参数的获取和验证。
##1.12.2 参数解析的配置规则
熟悉Yii的同学,对于以下的规则配置应该倍感亲切,但是不熟悉的同学也可以同样快速上手。因为,你会慢慢发现,这样的规则很符合我们PHP开发的规范,如果没有,我们继续努力改进。
格式如下:
```javascript
array(
'参数名' => array('name' => '接口参数名称', 'type' => '类型', 'default' => '默认值', ...),
... ...
)
```
##1.12.3 示例
###(1)简单的示例
假设这样的业务场景,我们需要提供一个用户登录的接口,其中需要用户名和密码,因此:
```javascript
<?php
class Api_User extends PhalApi_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
```
就可以获取到需要的参数:
```javascript
{"ret":0,"data":{"username":"test","password":"123456"},"msg":""}
```
从中,可以很容易理解:参数规则需要统一配置在接口实现类里面的 **getRules()** 函数,随后即可以通过类成员属性方式获取,如: **$this->username** 。
###(2)更完善的示例
很多时候我们都会对用户名和密码作一些验证,如是否必须、长度、最值,以及默认值等。
继续上面的业务场景,我们登录下用户名和密码必须,且密码长度至少为6个字符,则可以调整参数规则:
```javascript
'login' => array(
'username' => array('name' => 'username', 'require' => true),
'password' => array('name' => 'password', 'require' => true, 'min' => 6),
),
```
尝试一下非法的参数请求,如无任何参数的情况下,访问/?service=User.Login,返回:
```javascript
{"ret":400,"data":[],"msg":"Illegal Param: wrong param: username"}
```
再尝试一下密码长不对的情况,访问/?service=User.Login&username=test&password=123,返回:
```javascript
{"ret":400,"data":[],"msg":"Illegal Param: password.len should >= 6, but now password.len = 3"}
```
##1.12.4 三级参数
###(1)系统参数
已被系统固定占有的参数,目前只有一个,即:service,为需要调用的服务,类型为字符串,格式为:XXX.XXX,首字母不区分大小写,建议统一以大写开头。
以下是一些示例:
```javascript
#推荐写法
/?service=User.GetBaseInfo
#正确写法(开头小写)
/?service=user.getBaseInfo
#正确写法(方法名小写,但类名只能开头小写,否则会导致linux系统下文件加载失败)
/?service=user.getbaseinfo
#错误写法(缺少方法名)
/?service=User
#错误写法(缺少点号分割)
/?service=UserGetBaseInfo
#错误写法(默认只支持点号分割)
/?service=User|GetBaseInfo
```
###(2)应用参数
应用参数是指在一个项目中,全部接口都需要的参数,或者通用的参数规则。假如我们的项目中全部需要签名sign参数,且必须;以及非必须的版本号,则可以在./Config/app.php中的apiCommonRules配置:
```javascript
//$vim ./Config/app.php
<?php
return array(
/**
* 应用接口层的统一参数
*/
'apiCommonRules' => array(
//签名
'sign' => array(
'name' => 'sign', 'require' => true,
),
//客户端App版本号,如:1.0.1
'version' => array(
'name' => 'version', 'default' => '',
),
),
... ...
```
###(3)接口参数
接口参数即为上面在各个接口子类中配置的规则,为特定接口所持有。同时,为了方便同一套接口的规则重用,可以使用下标为 '*' 表示是本接口通用规则,如我们为了加强安全性,为全部的用户接口操作都加上4位的验证码:
```javascript
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),
),
);
}
```
在完成对上面的应用参数规则、接口通用规则和指定规则的参数进行配置后,对用户登录的接口进行请求时就需要这样访问:
```javascript
/?service=User.login&sign=77f81c17d512302383e5f26b99dae4d9&username=test&password=123456&code=abcd
```
> 温馨提示:
> 在Api类里面配置规则时,下标不区分大小写。因为框架会自动将请求的函数名和全部的规则下标转换成小写进行匹配。
这里,再小结一下,接口参数可以分为两种: **通用接口参数** 和 **指定接口参数** 。前者用 * 号下标表示,后者则用函数名作为下标表示。
###(4)多个参数规则的优先级
当同一个参数规则分别在应用参数、接口通用参数及特定接口参数出现时,后面的规则会覆盖前面的,即具体化的规则会替换通用的规则,以保证接口在特定场合的定制性。
简而言之,多个参数规则的优先级从高到下,分别是(正如你想到的那样):
+ 1、指定接口参数
+ 2、通用接口参数
+ 3、应用参数
+ 4、系统参数(通常忽略,因为只有service)
##1.12.5 在线接口参数查询工具
为了便于理解上面全部的参数规则,对于具体接口调用的要求,这里可以使用在线接口参数查询工具在浏览器访问查看:
```javascript
/demo/checkApiParams.php?service=User.Login
```
可以看到:
![show](http://webtools.qiniudn.com/20150411005257_9059c8a741f03da3b099f9042ced8678)
此工具同时也可以方便客户端实时查看接口文档时,进行辅助的接口规则说明。
###自描述数据
这里值得一提的是,我们这里所定义的参数规则实际上也是自描述数据。即配置的代码真实同步反映了参数的相关属性。
##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|应为字符串|可选|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
时间戳/日期|date|true/false,默认false|会按格式转换|可选,仅当为timestamp时才判断|格式:format 为timestamp时会将字符串的日期转换
数组|array|true/false,默认false|为非数组会自动转换/解析成数组|可选,判断数组元素个数|格式:format 为explode时,会根据separator将字符串分割成数组, 为json时,会json解析
枚举|enum|true/false,默认false|应为range中的某个元素|---|必须,range,以数组指定枚举的范围
文件|file|true/false,默认false|数组类型|min和max表示文件大小范围|range下标表示允许上传的文件类型,ext表示需要过滤的文件扩展名
回调|callable|true/false,默认false|---|callback设置回调函数,params为回调函数的第三个参数,第一个为参数值,第二个为所配置的规则
> 温馨提示:
> 全部的参数规则,都可以配置desc下标,对应在线接口文档的”说明“部分。
> 如: array('name' => 'username', 'desc' => '用户名')
下面是对各类型的示例说明。
###(1)字符串 string
当一个参数规则 未指定类型时,默认为string。一个完整的写法可以为:
```javascript
array('name' => 'username', 'type' => 'string', 'require' => true, 'default' => 'nobody', 'min' => 1, 'max' => 10)
```
若传递的参数长度过长,如&username=alonglonglonglongname,则会异常失败返回:
```javascript
{"ret":400,"data":[],"msg":"Illegal Param: username.len should <= 10, but now username.len = 21"}
```
但是当需要验证的是类型是中文的话会出现一点问题一个中文字符会占用3个字节所以在min和max验证的时候会出现一些问题,PhalApi提供了format方式对你需要验证长度的string进行指定格式可以排除此问题
```javascript
array('name' => 'username', 'type' => 'string','format' => 'utf8', 'min' => 1, 'max' => 10)
```
###(2)整型 int
如通常数据库中的id,即可配置成:
```javascript
array('name' => 'id', 'type' => 'int', 'require' => true, 'min' => 1 )
```
当传递的参数,不在其配置的范围内时,如&id=0,则会异常失败返回:
```javascript
{"ret":400,"data":[],"msg":"Illegal Param: id should >= 1, but now id = 0"}
```
###(3)浮点 float
浮点型,类似整型的配置,此处略。
###(4)布尔值 boolean
布尔值,主要是可以对一些字符串转换成布尔值,如ok, true, success, on, yes, 以及会被PHP解析成true的字符串,都会转换成true,方便调用。如通常的是否记住我:
```javascript
array('name' => 'isRememberMe', 'type' => 'boolean', 'default' => true)
```
###(5)日期 date
日期可以按自己约定的格式传递,当需要将字符串的日期转换成timestamp时,可以这样配置:
```javascript
array('name' => 'registerData', 'type' => 'date')
```
对应®isterData=2015-01-31%2010:00:00则会被获取到为:"2015-01-31 10:00:00"。
如果是配置成:
```javascript
array('name' => 'registerData', 'type' => 'date', 'format' => 'timestamp')
```
则上面的参数再请求时,则会被转换成:1422669600。
###(6)数组 array
很多时候在接口进行批量获取时,都需要提供一组参数,所以这时可以使用数组来进行配置。如:
```javascript
array('name' => 'uids', 'type' => 'array', 'format' => 'explode', 'separator' => ',')
```
对应&uids=1,2,3则会被转换成:
```javascript
array ( 0 => '1', 1 => '2', 2 => '3', )
```
又如接口需要使用JSON来传递整块参数时,可以这样配置:
```javascript
array('name' => 'params', 'type' => 'array', 'format' => 'json')
```
对应¶ms={"username":"test","password":"123456"}则会被转换成:
```javascript
array ( 'username' => 'test', 'password' => '123456', )
```
特别地,当配置成了数组,却未指定格式format时,会转换成一个元素的数组,如:&name=test,会转换成:array('test')。
###(7)枚举 enum
在需要对接口参数进行范围限制时,可以使用此枚举型。如对于性别的参数,可以这样配置:
```javascript
array('name' => 'sex', 'type' => 'enum', 'range' => array('female', 'male'))
```
当传递的参数不合法时,如&sex=unknow,则会被拦截,返回失败:
```javascript
{"ret":400,"data":[],"msg":"Illegal Param: sex should be in female\/male, but now sex = unknow"}
```
关于枚举类型的配置,这里需要特别注意配置时,应尽量使用字符串的值。
因为通常而言,接口通过GET/POST方式获取到的参数都是字符串的,而如果配置规则时指定范围用了整型,会导致底层规则验证时误。如:
```javascript
//接口参数为: &type=N
//接口参数规则为:
array('name' => 'type', 'type' => 'enum', 'range' => array(0, 1, 2))
//误判,因为:
var_dump(in_array('N', array(0, 1, 2))); //结果为true,因为 'N' == 0
```
为了避免这类情况发生,应该这样配置:
```javascript
//接口参数规则为(使用字符串):
array('name' => '&type', 'type' => 'enum', 'range' => array(`0`, `1`, `2`))
```
###(8)文件 file
在需要对上传的文件进行过滤、接收和处理时,可以使用文件类型,如:
```javascript
array(
'name' => 'upfile',
'type' => 'file',
'min' => 0,
'max' => 1024 * 1024,
'range' => array('image/jpeg', 'image/png') ,
'ext' => array('txt','xml')
)
```
其中,min和max分别对应文件大小的范围,单位为字节;range为允许的文件类型,使用数组配置,且不区分大小写。
如果成功,返回的值对应的是$_FILES["upfile"],即会返回:
```javascript
array(
'name' => '',
'type' => '',
'size' => '',
'tmp_name' => '',
)
```
对应的是:
+ $_FILES["upfile"]["name"] - 被上传文件的名称
+ $_FILES["upfile"]["type"] - 被上传文件的类型
+ $_FILES["upfile"]["size"] - 被上传文件的大小,以字节计
+ $_FILES["upfile"]["tmp_name"] - 存储在服务器的文件的临时副本的名称
+ $_FILES["upfile"]["error"] - 由文件上传导致的错误代码
若需要配置默认值default选项,则也应为一数组,且其格式应类似如上。
其中,ext是对文件后缀名进行验证,当如果上传文件后缀名不匹配时将抛出异常。文件扩展名的过滤可以类似这样进行配置:
```
//单个后缀名 - 数组形式
'ext' => array('jpg')
//单个后缀名 - 字符串形式
'ext' => 'jpg'
//多个后缀名 - 数组形式
'ext' => array('jpg', 'jpeg', 'png', 'bmp')
//多个后缀名 - 字符串形式(以英文逗号分割)
'ext' => 'jpg,jpeg,png,bmp'
```
###(9)回调 callable
当需要利用已有函数进行自定义验证时,可采用回调参数规则,如:
```
//配置规则
array('name' => 'version', 'type' => 'callable', 'callback' => array('Common_MyVersion', 'formatVersion'))
```
然后,回调时将调用下面这个函数:
```
//新增一个自定义的版本检测函数
class Common_MyVersion {
public static function formatVersion($value, $rule) {
if (count(explode('.', $value)) < 3) {
throw new PhalApi_Exception_BadRequest('版本号格式错误');
}
}
}
```
> 温馨提示:
> 第一个为参数值,第二个为所配置的规则,第三个参数为配置规则中的params(可忽略)
##1.12.8 关于参数设计的原则
###(1)通配的$_REQUEST
使用$_REQUEST获取参数,便于在不同场合下GET/POST之间的切换,同时在初始化DI()->request服务时,可以指定传递的参数,以便于灵活的单元测试;
###(2)更自由的名称映射
之所以没把规则配置的下标默认成与客户端传递的name一致,是为了更自由的名称映射;
如可能我们PHP后台开发喜欢用驼峰法来表示,但客户端想用下划线来分割,则通过这样配置:
```javascript
array(
'isRememberMe' => array('name' => 'is_remember_me', 'type' => 'boolean', 'default' => true),
)
```
更重要的是,有时我们希望能缩短客户端请求的参数名称以节省流量时,可以这样配置:
```javascript
array(
'isRememberMe' => array('name' => 're', 'type' => 'boolean', 'default' => true),
)
```
###(3)异常返回
对于客户端参数不合法时,以异常失败返回,而不是隐性地转换,是因为后台接口往往需要手动对传递的参数进行人工的验证,而不是希望得到隐性转换的值。即当客户端参数传递不对时,我们需要明确提示说:参数非法。
##1.12.9 扩展你的参数
当PhalApi提供的参数规则不能满足接口参数的规则验证时,除了使用callable类型进行扩展外,还可以扩展PhalApi_Request_Formatter接口来定制项目需要的类型。
一如既往,分两步:
+ 1、扩展实现PhalApi_Request_Formatter接口
+ 2、在DI注册你的类型
下面以大家所熟悉的邮件类型为例,说明扩展的步骤。
首先,我们需要一个实现了邮件类型验证的功能类:
```
<?php
class Common_MyFormatter_Email implements PhalApi_Request_Formatter {
public function parse($value, $rule) {
if (!preg_match('/^(\w)+(\.\w+)*@(\w)+((\.\w+)+)$/', $value)) {
throw new PhalApi_Exception_BadRequest('邮箱地址格式错误');
}
return $value;
}
}
```
然后,注册一下:
```
DI()->_formatterEmail = 'Common_MyFormatter_Email';
```
> 温馨提示:
> 在DI中手动注册服务时,名称的格式为: 下划线("_") + 统一前缀("formatter") + 参数类型(全部小写后,首字母大写);
> 若需要实现自动注册,扩展的类名格式须为:
```
class PhalApi_Request_Formatter_{类型名称} implements PhalApi_Request_Formatter { ...
```
系统已自动注册的格式化服务有:
+ _formatterArray 数组格式化服务
+ _formatterBoolean 布尔值格式化服务
+ _formatterCallable 回调格式化服务
+ _formatterDate 日期格式化服务
+ _formatterEnum 枚举格式化服务
+ _formatterFile 上传文件格式化服务
+ _formatterFloat 浮点数格式化服务
+ _formatterInt 整数格式化服务
+ _formatterString 字符串格式化服务
至此,便可使用自己定制的类型规则了,
```
array('name' => 'user_email', 'type' => 'email')
```
- 欢迎使用PhalApi!
- 接口,从简单开始!
- [1.1]-下载与安装
- [1.2]-创建一个自己的项目
- [1.3]-在线体验
- [1.4]-文档、帮助和官网
- [1.10]-对PhalApi框架的抉择
- [1.11]-快速入门(backup)
- [1.12]-参数规则:接口参数规则配置
- [1.13]-统一的接口请求方式:_sevice=XXX.XXX
- [1.14]-统一的返回格式和结构:ret-data-msg
- [1.15]-数据库操作:基于NotORM的使用及优化
- [1.16]-配置读取:内外网环境配置的完美切换
- [1.17]-日记纪录:简化版的日记接口
- [1.18]-快速函数:人性化的关怀
- [1.19]-DI服务速查:各资源服务一览表
- [1.20]-DB操作:数据库基本操作速查
- [1.21]-类的自动加载:遵循PEAR包的命名规范
- [1.22]-签名验证:自定义签名规则
- [1.23]-请求和响应:GET和POST两者皆可得及超越JSON格式返回
- [1.24]-缓存策略:更灵活地可配置化的多级缓存
- [1.25]-国际化翻译:为走向国际化提前做好翻译准备
- [1.26]-数据安全:数据对称加密方案
- [1.27]-精益开发:更富表现力的Model层和重量级数据获取的应对方案
- [1.28]-COOKIE:对COOKIE原生态的支持及记忆加密升级版
- [1.29]-开放与封闭:多入口和统一初始化
- [1.30]-保持的力量:接口开发最佳实践
- [1.31]-新型计划任务:以接口形式实现的计划任务
- [2.11]-核心思想:DI依赖注入-让资源更可控
- [2.12]-海量数据:可配置的分库分表
- [2.13]-接口调试:在线SQL语句查看与性能优化
- [2.14]-测试驱动开发:意图导向编程下的接口开发
- [2.15]-演进:新型计划任务续篇
- [2.16]-领域驱动设计:应对复杂领域业务的Domain层
- [2.17]-微服务:Api接口服务层
- [2.18]-定制化:资源服务的再实现
- [2.19]-扩展库:可重用的扩展类库
- [2.20]-约定编程:架构明显的编程风格
- [2.21]-服务器统一部署方案简明版:CentOs---Nginx---php-fpm---MySql-[--Memcached]
- [2.22]-更多工具:精益项目和团队建设
- [3.1]-扩展类库:微信开发
- [3.2]-扩展类库:代理模式下phprpc协议的轻松支持
- [3.3]-扩展类库:基于PHPMailer的邮件发送
- [3.4]-扩展类库:优酷开放平台接口调用
- [3.5]-扩展类库:七牛云存储接口调用
- [3.6]-扩展类库:新型计划任务
- [3.8]-扩展类库:用户、会话和第三方登录集成
- [3.9]-扩展类库:swoole支持下的长链接和异步任务实现
- [3.11]-扩展类库:基于FastRoute的快速路由
- [4.2]-开发实战2:模拟优酷开放平台接口项目开发
- [4.3]-开发实战3:一个简单的小型项目开发(奔跑吧兄弟投票活动)
- [5.1]-架构与思想:PhalApi核心设计和思想解读
- [5.2]-杂谈:扯一些PhalApi的前世和今生
- [5.3]-框架总结:术语表和PHP开发建议
- [5.4]-许可
- [5.5]-联系和加入我们
- [5.6]-更新日记
- [5.8]-致框架贡献者:加入PhalApi开源指南
- [6.1]-基于接口查询语言的SDK包
- [6.2]-SDK包(JAVA版)
- [6.3]-SDK包(PHP版)
- [6.4]-SDK包(Objective-C版)
- [6.5]-SDK包(javascript版)
- [6.6]-SDK包(Ruby版)
- [8.1]-PhalApi视频教程
- 附录1:接口文档参考模板