一个真正的强者,不是摆平了多少人,而是他能帮助到多少人。 —开源中国源创会分享广州站 @海洋之心-悟空


1.11.1 模拟开发:获取开源中国用户信息接口

首次使用此接口开发框架时,可以先查看此开发示例。

假设,我们需要为开源中国打造一个平放平台,其中有一个接口是可以根据用户ID来获取用户的基本信息。

本文,就以模拟获取开源中国用户信息接口开发(即:从数据库获取用户的基本信息并以JSON格式返回给客户端)为例,简明的接口开发中的流程,以及用到的基本功能和操作,主要包括有:统一入口文件、参数规则配置、接口层/领域层/模型持久层、日记纪录、数据库操作、配置读取、国际化翻译等。

以给大家一个感观的认识。

最终接口的调用与返回结果如下:

  1. //接口请求格式
  2. http://dev.phalapi.com/demo/?service=User.GetBaseInfo&user_id=帐号ID
  3. //返回结果格式
  4. {
  5. "ret": 200,
  6. "data": {
  7. "code": 0, //状态码,0表示正常获取,1表示用户不存在
  8. "msg": "",
  9. "info": { //用户信息
  10. "id": "1", //用户ID
  11. "name": "dogstar", //帐号
  12. "note": "oschina" //来源
  13. }
  14. },
  15. "msg": ""
  16. }

1.11.2 开发流程

(1) 统一入口文件

为了更好保护我们的项目代码,建议将接口的访问路径设置在:./PhalApi/Pubic目录,并且各套接口(如按版本分:v1/v2/v3等等;按不同终端分:ios/android/pc等)各自独立入口。所以本示例中将./PhalApi/Pubic/demo/index.php下。

参数入口文件的写法,我们可以快速得到基本的入口文件如下:

  1. // $ vim ./Public/demo/index.php
  2. <?php
  3. /**
  4. * Demo 统一入口
  5. */
  6. require_once dirname(__FILE__) . '/../init.php';
  7. //装载你的接口
  8. DI()->loader->addDirs('Demo');
  9. /** ---------------- 响应接口请求 ---------------- **/
  10. $server = new PhalApi();
  11. $rs = $server->response();
  12. $rs->output();

此外,我们还需要一个公共的初始化文件:

  1. //$ vim ./Public/init.php
  2. <?php
  3. /**
  4. * 统一初始化
  5. */
  6. /** ---------------- 根目录定义,自动加载 ---------------- **/
  7. date_default_timezone_set('Asia/Shanghai');
  8. defined('API_ROOT') || define('API_ROOT', dirname(__FILE__) . '/..');
  9. require_once API_ROOT . '/PhalApi/PhalApi.php';
  10. $loader = new PhalApi_Loader(API_ROOT);
  11. /** ---------------- 注册&初始化服务组件 ---------------- **/
  12. //自动加载
  13. DI()->loader = $loader;
  14. //配置
  15. DI()->config = new PhalApi_Config_File(API_ROOT . '/Config');
  16. //日记纪录
  17. DI()->logger = new PhalApi_Logger_File(API_ROOT . '/Runtime',
  18. PhalApi_Logger::LOG_LEVEL_DEBUG | PhalApi_Logger::LOG_LEVEL_INFO | PhalApi_Logger::LOG_LEVEL_ERROR);
  19. //数据操作 - 基于NotORM,$_GET['__sql__']可自行改名
  20. DI()->notorm = function() {
  21. $debug = !empty($_GET['__sql__']) ? true : false;
  22. return new PhalApi_DB_NotORM(DI()->config->get('dbs'), $debug);
  23. };
  24. //调试模式,$_GET['__debug__']可自行改名
  25. DI()->debug = !empty($_GET['__debug__']) ? true : DI()->config->get('sys.debug');
  26. //翻译语言包设定
  27. SL('zh_cn');

(2)TDD测试驱动开发

遵循最佳实践,我们在编写代码前先编写单元测试。但同时为了减少编写测试代码的痛苦,我们可以先定义接口函数签名,再通过脚本自动生成测试代码骨架来提高我们的开发效率。

  1. //$vim ./Demo/Api/User.php
  2. <?php
  3. class Api_User extends PhalApi_Api
  4. {
  5. public function getBaseInfo()
  6. {
  7. }
  8. }

通过脚本生成测试骨架:

  1. $ mkdir -p ./Demo/Tests/Api/
  2. $ cd ./Demo/Tests/Api
  3. $ php ../../../PhalApi/build_phpunit_test_tpl.php ../../Api/User.php Api_User ./../test_env.php

然后,根据/Public/demo/index.php入口文件,也搭建一个测试环境的入口文件:

  1. vim ./Demo/Tests/test_env.php

修正一下Api_Examples_User_Test.php里,测试环境test_env.php的包含路径:

  1. //手动调用test_env.php的路径
  2. require_once dirname(__FILE__) . '/../test_env.php';

并且,修改测试,以符合我们通过userId=1获取基本信息(名字为dogstar,来源为oschina):

  1. //$vim ./Demo/Tests/Api/Api_User_Test.php
  2. /**
  3. * @group testGetBaseInfo
  4. */
  5. public function testGetBaseInfo()
  6. {
  7. $str = 'service=User.GetBaseInfo&user_id=1';
  8. parse_str($str, $params);
  9. DI()->request = new PhalApi_Request($params);
  10. $api = new Api_User();
  11. //自己进行初始化
  12. $api->init();
  13. $rs = $api->getBaseInfo();
  14. $this->assertNotEmpty($rs);
  15. $this->assertArrayHasKey('code', $rs);
  16. $this->assertArrayHasKey('msg', $rs);
  17. $this->assertArrayHasKey('info', $rs);
  18. $this->assertEquals(0, $rs['code']);
  19. $this->assertEquals('dogstar', $rs['info']['name']);
  20. $this->assertEquals('oschina', $rs['info']['note']);
  21. }

此时,单元测试是预期失败的:

  1. $ phpunit ./Api_User_Test.php
  2. PHPUnit 4.3.4 by Sebastian Bergmann.
  3. F
  4. Time: 3 ms, Memory: 5.25Mb
  5. There was 1 failure:
  6. 1) PhpUnderControl_ApiUser_Test::testGetBaseInfo
  7. Failed asserting that a NULL is not empty.
  8. FAILURES!
  9. Tests: 1, Assertions: 1, Failures: 1.

(3)Api接口层

此接口层,主要是负责响应客户端的请求,调用需要的领域层进行必要的服务功能提供。

配置参数规则

为了获取到用户ID,我们可以在getRules()函数里面定义参数规则,以便框架自动帮我们过滤获取需要的参数。

温馨提示:接口层的全部类成员函数都应以小写开头。但对外,函数首字母不区分大小写,因为框架会将请求的函数强制转换成小写再执行。 原因在于:1、我们坚持驼峰法的代码风格;2、对外界我们可以统一使用大写来提供服务名称,如:User.Login,这样更显专业。
  1. //$vim ./Demo/Api/User.php
  2. <?php
  3. class Api_User extends PhalApi_Api
  4. {
  5. public function getRules()
  6. {
  7. return array(
  8. 'getBaseInfo' => array(
  9. 'userId' => array('name' => 'user_id', 'type' => 'int', 'min' => 1, 'require' => true),
  10. ),
  11. );
  12. }
  13. //...

如上,我们就定义了getBaseInfo接口中的user_id参数,对应的变量名字为userId,整型,最小值为1,必须。

接口实现

  1. //$vim ./Demo/Api/User.php
  2. public function getBaseInfo()
  3. {
  4. $rs = array('code' => 0, 'msg' => '', 'info' => array());
  5. $domain = new Domain_User();
  6. $info = $domain->getBaseInfo($this->userId);
  7. if (empty($info)) {
  8. DI()->logger->debug('user not found', $this->userId);
  9. $rs['code'] = 1;
  10. $rs['msg'] = T('user not exists');
  11. return $rs;
  12. }
  13. $rs['info'] = $info;
  14. return $rs;
  15. }

(4)Domain领域层

领域层主要是关注复杂业务的处理,以及缓存的处理、耗时操作后台异步处理等,并调用Model持久层获取需要的数据。因此,是Api与Model层之间的桥梁。

在此示例中,我们只需要简单地调用Model层获取用户的信息即可,再加强一下用户ID的合法性判断。

  1. //$ vim ./Demo/Domain/User.php
  2. <?php
  3. class Domain_User
  4. {
  5. public function getBaseInfo($userId)
  6. {
  7. $rs = array();
  8. $userId = intval($userId);
  9. if ($userId <= 0) {
  10. return $rs;
  11. }
  12. $model = new Model_User();
  13. $rs = $model->getByUserId($userId);
  14. return $rs;
  15. }
  16. }

(5)Model持久层

此一层主要关注数据从持久存储的获取,特别是针对数据库的操作,但不排除其他媒介,如文件、缓存等。

首先,先准备一下我们需要的表和数据:

  1. CREATE TABLE `phalapi_test`.`tbl_user` (
  2. `id` INT NOT NULL,
  3. `name` VARCHAR(45) NULL,
  4. `note` VARCHAR(45) NULL,
  5. PRIMARY KEY (`id`));
  6. INSERT INTO `phalapi_test`.`tbl_user` (`id`, `name`, `note`) VALUES ('1', 'dogstar', 'oschina');

然后,编写需要的Model代码,即利用NotORm实现对数据的操作:

  1. //$ vim ./Demo/Model/User.php
  2. <?php
  3. class Model_User
  4. {
  5. public function getByUserId($userId)
  6. {
  7. return DI()->notorm->user->select('*')->where('id = ?', $userId)->fetch();
  8. }
  9. }

(6)单元测试通过啦!

在完成上面简单的开发后,我们即可以实现接口的开发,运行一下刚才的单元测试,完美通过!

  1. $ phpunit ./Api_User_Test.php
  2. PHPUnit 4.3.4 by Sebastian Bergmann.
  3. SELECT * FROM tbl_user WHERE (id = ?); -- 1
  4. .SELECT * FROM tbl_user WHERE (id = ?); -- 1<br />
  5. Time: 34 ms, Memory: 6.50Mb
  6. OK (2 tests, 7 assertions)

在单元测试的保证下,我们便可以放心大胆地将我们的代码发布到外网,提供给更多的开发者,和终端用户使用。

(7)数据库配置

因为是单元测试,所以我们配置搭建了新的一个测试环境,特别对于数据库的配置,如下:

  1. //$ vim ./Config/dbs.php
  2. <?php
  3. /**
  4. * examples配置
  5. */
  6. return array(
  7. /**
  8. * DB数据库服务器集群
  9. */
  10. 'servers' => array(
  11. 'db_demo' => array(
  12. 'host' => '192.168.0.104', //数据库域名
  13. 'name' => 'phalapi_test', //数据库名字
  14. 'user' => 'root', //数据库用户名
  15. 'password' => '123456', //数据库密码
  16. 'port' => '3306', //数据库端口
  17. ),
  18. ),
  19. /**
  20. * 自定义路由表
  21. */
  22. 'tables' => array(
  23. '__default__' => array(
  24. 'prefix' => 'tbl_',
  25. 'key' => 'id',
  26. 'map' => array(
  27. array('db' => 'db_demo'),
  28. ),
  29. ),
  30. ),
  31. );
温馨提示:为了方便在单元测试时进行调试,和查看日记,对于全部查询、执行的SQL语句都会显示出来,全部的日记改用控制台输出。

(8)最终接口调用

接口调用的链接,这时已经相当明了了,即:域名 + 路径(/demo) + 参数(可从单元测试那直接获取)

如本示例的是:

  1. http://dev.phalapi.com/demo/?service=User.GetBaseInfo&user_id=1

返回的结果是:

  1. {
  2. "ret": 200,
  3. "data": {
  4. "code": 0,
  5. "msg": "",
  6. "info": {
  7. "id": "1",
  8. "name": "dogstar",
  9. "note": "oschina"
  10. }
  11. },
  12. "msg": ""
  13. }

截图效果:show

温馨提示:如果提示日记写入失败,请确保./Runtime目录具有写入权限,即0777。

1.11.3 更多简明的使用

(1)日记纪录

当我们访问一个不存在的用户时,将会触发日记纪录:

  1. DI()->logger->debug('user not found', $this->userId);

如访问:

  1. http://dev.phalapi.com/demo/?service=User.GetBaseInfo&user_id=2

然后,可以在Runtime下看到按天分目录的日记:

  1. $ tailf ./Runtime/log/201501/20150128.log
  2. 2015-01-28 00:37:34|DEBUG|user not found|2
温馨提示:外网环境上,请把Runtime目录软链到磁盘空间很大的路径。

(2)国际化翻译

当需要翻译时,可以使用人性化的函数T(),如:

  1. $rs['msg'] = T('user not exists');

对应地需要补充翻译的内容:

  1. //$ vim ./Language/zh_cn/common.php
  2. 'user not exists' => '用户不存在',

还是以上面的用户不存在为例,看下运行的截图效果:show

(3)配置读取

配置的读取,使用方便,直接通过以下方式便可以获取,以点号分割:

  1. DI()->config->get('dbs')

第一段,必须为文件名,后面的为用点号相连的数组下标,不限级。

(4)[酷!]接口参数在线查询

为了方便客户端实时查看最新的接口参数,这里提供了一个快速的在线工具:

  1. http://demo.phalapi.net/checkApiParams.php?service=User.GetBaseInfo

用浏览器打开后,即可以看到最新的接口参数说明, 不需要后台接口开发人员编写文档维护,直接从代码中生成参数报表:

show

1.11.4 Demo相关代码文件

从上面可以得到,此示例相关的代码如下:

  1. Demo$ tree
  2. .
  3. ├── Api
  4. ├── Default.php
  5. └── User.php
  6. ├── Domain
  7. └── User.php
  8. ├── Model
  9. └── User.php
  10. └── Tests
  11. ├── Api
  12. ├── Api_Default_Test.php
  13. └── Api_User_Test.php
  14. └── test_env.php

原文: https://www.phalapi.net/wikis/1-11.html