一个真正的强者,不是摆平了多少人,而是他能帮助到多少人。 —开源中国源创会分享广州站 @海洋之心-悟空
1.11.1 模拟开发:获取开源中国用户信息接口
首次使用此接口开发框架时,可以先查看此开发示例。
假设,我们需要为开源中国打造一个平放平台,其中有一个接口是可以根据用户ID来获取用户的基本信息。
本文,就以模拟获取开源中国用户信息接口开发(即:从数据库获取用户的基本信息并以JSON格式返回给客户端)为例,简明的接口开发中的流程,以及用到的基本功能和操作,主要包括有:统一入口文件、参数规则配置、接口层/领域层/模型持久层、日记纪录、数据库操作、配置读取、国际化翻译等。
以给大家一个感观的认识。
最终接口的调用与返回结果如下:
//接口请求格式
http://dev.phalapi.com/demo/?service=User.GetBaseInfo&user_id=帐号ID
//返回结果格式
{
"ret": 200,
"data": {
"code": 0, //状态码,0表示正常获取,1表示用户不存在
"msg": "",
"info": { //用户信息
"id": "1", //用户ID
"name": "dogstar", //帐号
"note": "oschina" //来源
}
},
"msg": ""
}
1.11.2 开发流程
(1) 统一入口文件
为了更好保护我们的项目代码,建议将接口的访问路径设置在:./PhalApi/Pubic目录,并且各套接口(如按版本分:v1/v2/v3等等;按不同终端分:ios/android/pc等)各自独立入口。所以本示例中将./PhalApi/Pubic/demo/index.php下。
参数入口文件的写法,我们可以快速得到基本的入口文件如下:
// $ vim ./Public/demo/index.php
<?php
/**
* Demo 统一入口
*/
require_once dirname(__FILE__) . '/../init.php';
//装载你的接口
DI()->loader->addDirs('Demo');
/** ---------------- 响应接口请求 ---------------- **/
$server = new PhalApi();
$rs = $server->response();
$rs->output();
此外,我们还需要一个公共的初始化文件:
//$ vim ./Public/init.php
<?php
/**
* 统一初始化
*/
/** ---------------- 根目录定义,自动加载 ---------------- **/
date_default_timezone_set('Asia/Shanghai');
defined('API_ROOT') || define('API_ROOT', dirname(__FILE__) . '/..');
require_once API_ROOT . '/PhalApi/PhalApi.php';
$loader = new PhalApi_Loader(API_ROOT);
/** ---------------- 注册&初始化服务组件 ---------------- **/
//自动加载
DI()->loader = $loader;
//配置
DI()->config = new PhalApi_Config_File(API_ROOT . '/Config');
//日记纪录
DI()->logger = new PhalApi_Logger_File(API_ROOT . '/Runtime',
PhalApi_Logger::LOG_LEVEL_DEBUG | PhalApi_Logger::LOG_LEVEL_INFO | PhalApi_Logger::LOG_LEVEL_ERROR);
//数据操作 - 基于NotORM,$_GET['__sql__']可自行改名
DI()->notorm = function() {
$debug = !empty($_GET['__sql__']) ? true : false;
return new PhalApi_DB_NotORM(DI()->config->get('dbs'), $debug);
};
//调试模式,$_GET['__debug__']可自行改名
DI()->debug = !empty($_GET['__debug__']) ? true : DI()->config->get('sys.debug');
//翻译语言包设定
SL('zh_cn');
(2)TDD测试驱动开发
遵循最佳实践,我们在编写代码前先编写单元测试。但同时为了减少编写测试代码的痛苦,我们可以先定义接口函数签名,再通过脚本自动生成测试代码骨架来提高我们的开发效率。
//$vim ./Demo/Api/User.php
<?php
class Api_User extends PhalApi_Api
{
public function getBaseInfo()
{
}
}
通过脚本生成测试骨架:
$ mkdir -p ./Demo/Tests/Api/
$ cd ./Demo/Tests/Api
$ php ../../../PhalApi/build_phpunit_test_tpl.php ../../Api/User.php Api_User ./../test_env.php
然后,根据/Public/demo/index.php入口文件,也搭建一个测试环境的入口文件:
vim ./Demo/Tests/test_env.php
修正一下Api_Examples_User_Test.php里,测试环境test_env.php的包含路径:
//手动调用test_env.php的路径
require_once dirname(__FILE__) . '/../test_env.php';
并且,修改测试,以符合我们通过userId=1获取基本信息(名字为dogstar,来源为oschina):
//$vim ./Demo/Tests/Api/Api_User_Test.php
/**
* @group testGetBaseInfo
*/
public function testGetBaseInfo()
{
$str = 'service=User.GetBaseInfo&user_id=1';
parse_str($str, $params);
DI()->request = new PhalApi_Request($params);
$api = new Api_User();
//自己进行初始化
$api->init();
$rs = $api->getBaseInfo();
$this->assertNotEmpty($rs);
$this->assertArrayHasKey('code', $rs);
$this->assertArrayHasKey('msg', $rs);
$this->assertArrayHasKey('info', $rs);
$this->assertEquals(0, $rs['code']);
$this->assertEquals('dogstar', $rs['info']['name']);
$this->assertEquals('oschina', $rs['info']['note']);
}
此时,单元测试是预期失败的:
$ phpunit ./Api_User_Test.php
PHPUnit 4.3.4 by Sebastian Bergmann.
F
Time: 3 ms, Memory: 5.25Mb
There was 1 failure:
1) PhpUnderControl_ApiUser_Test::testGetBaseInfo
Failed asserting that a NULL is not empty.
FAILURES!
Tests: 1, Assertions: 1, Failures: 1.
(3)Api接口层
此接口层,主要是负责响应客户端的请求,调用需要的领域层进行必要的服务功能提供。
配置参数规则
为了获取到用户ID,我们可以在getRules()函数里面定义参数规则,以便框架自动帮我们过滤获取需要的参数。
温馨提示:接口层的全部类成员函数都应以小写开头。但对外,函数首字母不区分大小写,因为框架会将请求的函数强制转换成小写再执行。 原因在于:1、我们坚持驼峰法的代码风格;2、对外界我们可以统一使用大写来提供服务名称,如:User.Login,这样更显专业。
//$vim ./Demo/Api/User.php
<?php
class Api_User extends PhalApi_Api
{
public function getRules()
{
return array(
'getBaseInfo' => array(
'userId' => array('name' => 'user_id', 'type' => 'int', 'min' => 1, 'require' => true),
),
);
}
//...
如上,我们就定义了getBaseInfo接口中的user_id参数,对应的变量名字为userId,整型,最小值为1,必须。
接口实现
//$vim ./Demo/Api/User.php
public function getBaseInfo()
{
$rs = array('code' => 0, 'msg' => '', 'info' => array());
$domain = new Domain_User();
$info = $domain->getBaseInfo($this->userId);
if (empty($info)) {
DI()->logger->debug('user not found', $this->userId);
$rs['code'] = 1;
$rs['msg'] = T('user not exists');
return $rs;
}
$rs['info'] = $info;
return $rs;
}
(4)Domain领域层
领域层主要是关注复杂业务的处理,以及缓存的处理、耗时操作后台异步处理等,并调用Model持久层获取需要的数据。因此,是Api与Model层之间的桥梁。
在此示例中,我们只需要简单地调用Model层获取用户的信息即可,再加强一下用户ID的合法性判断。
//$ vim ./Demo/Domain/User.php
<?php
class Domain_User
{
public function getBaseInfo($userId)
{
$rs = array();
$userId = intval($userId);
if ($userId <= 0) {
return $rs;
}
$model = new Model_User();
$rs = $model->getByUserId($userId);
return $rs;
}
}
(5)Model持久层
此一层主要关注数据从持久存储的获取,特别是针对数据库的操作,但不排除其他媒介,如文件、缓存等。
首先,先准备一下我们需要的表和数据:
CREATE TABLE `phalapi_test`.`tbl_user` (
`id` INT NOT NULL,
`name` VARCHAR(45) NULL,
`note` VARCHAR(45) NULL,
PRIMARY KEY (`id`));
INSERT INTO `phalapi_test`.`tbl_user` (`id`, `name`, `note`) VALUES ('1', 'dogstar', 'oschina');
然后,编写需要的Model代码,即利用NotORm实现对数据的操作:
//$ vim ./Demo/Model/User.php
<?php
class Model_User
{
public function getByUserId($userId)
{
return DI()->notorm->user->select('*')->where('id = ?', $userId)->fetch();
}
}
(6)单元测试通过啦!
在完成上面简单的开发后,我们即可以实现接口的开发,运行一下刚才的单元测试,完美通过!
$ phpunit ./Api_User_Test.php
PHPUnit 4.3.4 by Sebastian Bergmann.
SELECT * FROM tbl_user WHERE (id = ?); -- 1
.SELECT * FROM tbl_user WHERE (id = ?); -- 1<br />
Time: 34 ms, Memory: 6.50Mb
OK (2 tests, 7 assertions)
在单元测试的保证下,我们便可以放心大胆地将我们的代码发布到外网,提供给更多的开发者,和终端用户使用。
(7)数据库配置
因为是单元测试,所以我们配置搭建了新的一个测试环境,特别对于数据库的配置,如下:
//$ vim ./Config/dbs.php
<?php
/**
* examples配置
*/
return array(
/**
* DB数据库服务器集群
*/
'servers' => array(
'db_demo' => array(
'host' => '192.168.0.104', //数据库域名
'name' => 'phalapi_test', //数据库名字
'user' => 'root', //数据库用户名
'password' => '123456', //数据库密码
'port' => '3306', //数据库端口
),
),
/**
* 自定义路由表
*/
'tables' => array(
'__default__' => array(
'prefix' => 'tbl_',
'key' => 'id',
'map' => array(
array('db' => 'db_demo'),
),
),
),
);
温馨提示:为了方便在单元测试时进行调试,和查看日记,对于全部查询、执行的SQL语句都会显示出来,全部的日记改用控制台输出。
(8)最终接口调用
接口调用的链接,这时已经相当明了了,即:域名 + 路径(/demo) + 参数(可从单元测试那直接获取)
如本示例的是:
http://dev.phalapi.com/demo/?service=User.GetBaseInfo&user_id=1
返回的结果是:
{
"ret": 200,
"data": {
"code": 0,
"msg": "",
"info": {
"id": "1",
"name": "dogstar",
"note": "oschina"
}
},
"msg": ""
}
截图效果:
温馨提示:如果提示日记写入失败,请确保./Runtime目录具有写入权限,即0777。
1.11.3 更多简明的使用
(1)日记纪录
当我们访问一个不存在的用户时,将会触发日记纪录:
DI()->logger->debug('user not found', $this->userId);
如访问:
http://dev.phalapi.com/demo/?service=User.GetBaseInfo&user_id=2
然后,可以在Runtime下看到按天分目录的日记:
$ tailf ./Runtime/log/201501/20150128.log
2015-01-28 00:37:34|DEBUG|user not found|2
温馨提示:外网环境上,请把Runtime目录软链到磁盘空间很大的路径。
(2)国际化翻译
当需要翻译时,可以使用人性化的函数T(),如:
$rs['msg'] = T('user not exists');
对应地需要补充翻译的内容:
//$ vim ./Language/zh_cn/common.php
'user not exists' => '用户不存在',
还是以上面的用户不存在为例,看下运行的截图效果:
(3)配置读取
配置的读取,使用方便,直接通过以下方式便可以获取,以点号分割:
DI()->config->get('dbs')
第一段,必须为文件名,后面的为用点号相连的数组下标,不限级。
(4)[酷!]接口参数在线查询
为了方便客户端实时查看最新的接口参数,这里提供了一个快速的在线工具:
http://demo.phalapi.net/checkApiParams.php?service=User.GetBaseInfo
用浏览器打开后,即可以看到最新的接口参数说明, 不需要后台接口开发人员编写文档维护,直接从代码中生成参数报表:
1.11.4 Demo相关代码文件
从上面可以得到,此示例相关的代码如下:
Demo$ tree
.
├── Api
│ ├── Default.php
│ └── User.php
├── Domain
│ └── User.php
├── Model
│ └── User.php
└── Tests
├── Api
│ ├── Api_Default_Test.php
│ └── Api_User_Test.php
└── test_env.php